Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Non parts props work + examples in docs. #4

Merged
merged 2 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified bun.lockb
Binary file not shown.
260 changes: 260 additions & 0 deletions dev/components/demos/base-demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
import { clsx } from 'clsx';
import {
children,
ComponentProps,
createSignal,
createUniqueId,
FlowProps,
JSX,
mergeProps,
Show,
splitProps,
} from 'solid-js';

// Tabs
import { Tabs } from '@kobalte/core/tabs';

// TODO: Dropdown (use @kobalte)
// Dropdown
// import {
// Menu,
// MenuButton,
// MenuItem,
// MenuItems,
// Switch,
// Field,
// Label,
// type SwitchProps,
// type MenuButtonProps,
// type MenuItemProps,
// type MenuItemsProps,
// type MenuProps
// } from '@headlessui/react'

type TabValue = 'preview' | 'code';

// Switch
import { Switch } from '@kobalte/core/switch';

// ===========================================================================
// UI
// ===========================================================================

export type DemoProps = {
ref?: HTMLDivElement;
children: JSX.Element;
class?: string;
defaultValue?: TabValue;
code?: JSX.Element;
minHeight?: string;
title?: JSX.Element;
};

type Props = DemoProps & { onClick?: () => void };

function Demo(props: FlowProps<Props>) {
const _props = mergeProps(
{
defaultValue: 'preview',
minHeight: 'min-h-[20rem]',
children: undefined,
renderTitle: false,
},
props,
);

const [knowsToClick, setKnowsToClick] = createSignal(false);
const [active, setActive] = createSignal(_props.defaultValue);

const id = createUniqueId();

function handleClick() {
setKnowsToClick(true);
_props?.onClick?.();
}

const handleMouseDown: JSX.EventHandler<HTMLElement, MouseEvent> = (event) => {
// Prevent selection of text:
// https://stackoverflow.com/a/43321596
if (event.detail > 1) {
event.preventDefault();
}
};

/** Prevent doublle-render when using it in <Show /> https://github.com/solidjs/solid/issues/2345#issuecomment-2427189199 */
const renderedTitle = children(() => _props.title);

return (
<Tabs
ref={_props.ref}
class={clsx(active() === 'code' && 'dark', 'Demo text-primary not-prose relative isolate')} // reset text color if inside prose
value={active()}
onChange={(val) => setActive(val as TabValue)}
// onValueChange={(val) => setActive(val as TabValue)}
>
<Show when={_props.code}>
{/* <MotionConfig transition={{ layout: { type: 'spring', duration: 0.25, bounce: 0 } }}> */}
<Tabs.List class="bg-zinc-150/90 absolute right-3 top-3 z-10 flex gap-1 rounded-full p-1 backdrop-blur-lg dark:bg-black/60">
<Tabs.Trigger
value="preview"
class={clsx(
active() !== 'preview' && 'hover:transition-[color]',
'dark:text-muted hover:text-primary aria-selected:text-primary relative px-2 py-1 text-xs/4 font-medium text-zinc-600',
)}
>
<Show when={active() === 'preview'}>
{/* // Motion.div */}
<div
class="prefers-dark:!bg-white/15 absolute inset-0 -z-10 size-full bg-white shadow-sm dark:bg-white/25"
style={{ 'border-radius': '999' }}
// layout
// layoutId={`${id}active`}
></div>
</Show>
Preview
</Tabs.Trigger>
<Tabs.Trigger
value="code"
class={clsx(
active() !== 'code' && 'hover:transition-[color]',
'dark:text-muted hover:text-primary aria-selected:text-primary relative px-2 py-1 text-xs/4 font-medium text-zinc-600',
)}
>
<Show when={active() === _props.code}>
{/* // Motion.div */}
<div
class="prefers-dark:!bg-white/15 absolute inset-0 -z-10 size-full bg-white shadow-sm dark:bg-white/25"
style={{ 'border-radius': '999' }}
// layout
// layoutId={`${id}active`}
></div>
</Show>
Code
</Tabs.Trigger>
</Tabs.List>
{/* </MotionConfig> */}
</Show>
<Tabs.Content
value="preview"
class={clsx(
_props.class,
'border-faint relative rounded-lg border data-[state=inactive]:hidden',
)}
>
<Show when={renderedTitle()}>
<div class="absolute left-3 top-3">{renderedTitle()}</div>
</Show>

<div
class={clsx(_props.minHeight, 'flex flex-col items-center justify-center p-5 pb-6')}
onClick={_props?.onClick && handleClick}
onMouseDown={_props?.onClick && handleMouseDown}
>
{_props.children}
{_props?.onClick && (
<span
class={clsx(
'absolute bottom-5 left-0 w-full text-center text-sm text-zinc-400 transition-opacity duration-200 ease-out',
knowsToClick() && 'opacity-0',
)}
>
Click anywhere to change numbers
</span>
)}
</div>
</Tabs.Content>
<Show when={_props.code}>
<Tabs.Content value="code">{_props?.code}</Tabs.Content>«
</Show>
</Tabs>
);
}

export default Demo;

export function DemoTitle(props: JSX.IntrinsicElements['span'] & { children: string }) {
const [_props, other] = splitProps(props, ['class', 'children']);

return (
<span {...other} class={clsx(props.class, 'px-2 py-1.5 text-sm')}>
{props.children}
</span>
);
}

// export function DemoMenu(props: MenuProps) {
// return <Menu {...props} />
// }

// export function DemoMenuButton({
// children,
// class,
// ...props
// }: MenuButtonProps & { children: JSX.Element }) {
// return (
// <MenuButton
// {...props}
// class={clsx(
// class,
// 'group flex h-8 items-center rounded-full px-3 text-xs shadow-sm ring ring-black/[8%] dark:shadow-none dark:ring-white/10'
// )}
// >
// {children}
// <ChevronDown
// class="spring-bounce-0 spring-duration-150 ml-1 size-4 shrink-0 group-data-[active]:rotate-180"
// strokeWidth={2}
// />
// </MenuButton>
// )
// }

// export function DemoMenuItems({ class, ...props }: MenuItemsProps) {
// return (
// <MenuItems
// {...props}
// class={clsx(
// class,
// 'animate-pop-in dark:ring-white/12.5 absolute left-0 top-full mt-2 min-w-full origin-top-left rounded-xl bg-white/90 p-1.5 shadow-sm ring ring-inset ring-black/[8%] backdrop-blur-xl backdrop-saturate-[140%] dark:bg-zinc-950/90 dark:shadow-none'
// )}
// />
// )
// }

// export function DemoMenuItem({
// class,
// children,
// ...props
// }: MenuItemProps<'button'> & { children: JSX.Element }) {
// return (
// <MenuItem
// {...props}
// as="button"
// class={clsx(
// class,
// props.disabled ? 'pr-2' : 'pr-4',
// 'dark:data-[focus]:bg-white/12.5 flex w-full items-center gap-2 rounded-lg py-2 pl-2 text-xs font-medium data-[disabled]:cursor-default data-[focus]:bg-black/5'
// )}
// >
// {children}
// {props.disabled && <Check class="ml-auto h-4 w-4" />}
// </MenuItem>
// )
// }

export function DemoSwitch(props: ComponentProps<typeof Switch>) {
const [_props, other] = splitProps(props, ['class', 'children']);

return (
<Switch {...other} class="flex items-center gap-x-2">
<Switch.Control
class={clsx(
props.class,
'group relative flex h-6 w-11 cursor-pointer rounded-full bg-zinc-200 p-0.5 transition-colors duration-200 ease-in-out focus:outline-none data-[checked]:bg-zinc-950 data-[focus]:outline-2 data-[focus]:outline-blue-500 dark:bg-zinc-800 dark:data-[checked]:bg-zinc-50',
)}
>
<Switch.Thumb class="spring-bounce-0 spring-duration-200 pointer-events-none inline-block size-5 rounded-full bg-white shadow-lg ring-0 transition-transform group-data-[checked]:translate-x-5 dark:bg-zinc-950" />
</Switch.Control>
<Switch.Label class="text-xs">{props.children as any}</Switch.Label>
</Switch>
);
}
35 changes: 35 additions & 0 deletions dev/components/demos/continuous.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Demo, { DemoSwitch, type DemoProps } from 'dev/components/demos/base-demo';

import { useCycle } from 'dev/hooks/use-cycle';
import { createSignal } from 'solid-js';
import NumberFlow from 'src';

const NUMBERS = [120, 140];

export default function ContinuousDemo(props: Omit<DemoProps, 'children' | 'code'>) {
const [value, cycleValue] = useCycle(NUMBERS);
const [continuous, setContinuous] = createSignal(false);

return (
<>
<Demo
{...props}
title={
<DemoSwitch checked={continuous()} onChange={setContinuous}>
<code class="font-semibold">continuous</code>
</DemoSwitch>
}
onClick={cycleValue}
>
<div class="~text-xl/4xl flex items-center gap-4">
<NumberFlow
continuous={continuous()}
style={{ '--number-flow-char-height': '0.85em' }}
value={value()}
class="text-4xl font-semibold"
/>
</div>
</Demo>
</>
);
}
35 changes: 35 additions & 0 deletions dev/components/demos/trend.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Demo, { type DemoProps } from 'dev/components/demos/base-demo';

import { useCycle } from 'dev/hooks/use-cycle';
import { Trend } from 'number-flow';
import NumberFlow from 'src';

const NUMBERS = [19, 20];

export default function TrendDemo(props: Omit<DemoProps, 'children' | 'code'>) {
const [value, cycleValue] = useCycle(NUMBERS);
const [trend, cycleTrend] = useCycle([true, false, 'increasing', 'decreasing'] as Trend[]);

return (
<>
<Demo
{...props}
title={
<button class="transition active:scale-95" onClick={cycleTrend}>
<code class="text-xs font-semibold">trend: {JSON.stringify(trend())}</code>
</button>
}
onClick={cycleValue}
>
<div class="~text-xl/4xl flex items-center gap-4">
<NumberFlow
trend={trend()}
style={{ '--number-flow-char-height': '0.85em' }}
value={value()}
class="text-4xl font-semibold"
/>
</div>
</Demo>
</>
);
}
25 changes: 25 additions & 0 deletions dev/hooks/use-cycle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { createMemo, createSignal } from 'solid-js';

/**
* A hook that toggles between two or multiple values (by implementing a common state pattern).
*
* Forked from https://github.com/Blankeos/bagon-hooks/blob/main/src/use-toggle/use-toggle.ts
*/
export function useCycle<T = boolean>(options: readonly T[] = [false, true] as any) {
const [_options, _setOptions] = createSignal<typeof options>(options);

function toggle() {
const value = _options()[0]!;
const index = Math.abs(_options()!.indexOf(value));

_setOptions(
_options()!
.slice(index + 1)
.concat(value),
);
}

const currentOption = createMemo(() => _options()[0]!);

return [currentOption, toggle] as const;
}
Loading
Loading