-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from Blankeos/feat/none-parts
feat: Non parts props work + examples in docs.
- Loading branch information
Showing
9 changed files
with
566 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.