Skip to content

Commit 5bf3322

Browse files
committed
docs: extremely polished docs.
1 parent 1880249 commit 5bf3322

File tree

11 files changed

+248
-122
lines changed

11 files changed

+248
-122
lines changed

bun.lockb

401 Bytes
Binary file not shown.

dev/components/demos/base-demo.tsx

Lines changed: 205 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Tabs
22
import { Tabs } from "@kobalte/core/tabs"
3+
import { useClipboard } from "bagon-hooks"
34
import { clsx } from "clsx"
45
import {
56
type ComponentProps,
@@ -8,9 +9,13 @@ import {
89
type FlowProps,
910
type JSX,
1011
mergeProps,
12+
onCleanup,
13+
onMount,
1114
Show,
1215
splitProps,
1316
} from "solid-js"
17+
import { cn } from "@/utils/cn"
18+
import { MDXContent } from "dev/components/mdx-content"
1419

1520
// TODO: Dropdown (use @kobalte)
1621
// Dropdown
@@ -43,7 +48,7 @@ export type DemoProps = {
4348
children: JSX.Element
4449
class?: string
4550
defaultValue?: TabValue
46-
code?: JSX.Element
51+
code?: string
4752
minHeight?: string
4853
title?: JSX.Element
4954
}
@@ -64,6 +69,8 @@ function Demo(props: FlowProps<Props>) {
6469
const [knowsToClick, setKnowsToClick] = createSignal(false)
6570
const [active, setActive] = createSignal(_props.defaultValue)
6671

72+
const { copy, copied } = useClipboard()
73+
6774
function handleClick() {
6875
if (!_props.onClick) return
6976

@@ -87,25 +94,25 @@ function Demo(props: FlowProps<Props>) {
8794
return (
8895
<Tabs
8996
ref={_props.ref}
90-
class={clsx(active() === "code" && "dark", "Demo not-prose relative isolate text-primary")} // reset text color if inside prose
97+
class={clsx("Demo not-prose relative flex flex-col text-primary")} // reset text color if inside prose
9198
value={active()}
9299
onChange={(val) => setActive(val as TabValue)}
93100
// onValueChange={(val) => setActive(val as TabValue)}
94101
>
95102
<Show when={_props.code}>
96103
{/* <MotionConfig transition={{ layout: { type: 'spring', duration: 0.25, bounce: 0 } }}> */}
97-
<Tabs.List class="absolute top-3 right-3 z-10 flex gap-1 rounded-full bg-zinc-150/90 p-1 backdrop-blur-lg dark:bg-black/60">
104+
<Tabs.List class="absolute top-3 right-3 z-10 flex gap-1 rounded-full bg-black/60 p-1 backdrop-blur-lg">
98105
<Tabs.Trigger
99106
value="preview"
100107
class={clsx(
101108
active() !== "preview" && "hover:transition-[color]",
102-
"relative px-2 py-1 font-medium text-xs/4 text-zinc-600 hover:text-primary aria-selected:text-primary dark:text-muted"
109+
"relative px-2 py-1 font-medium text-muted text-xs/4 hover:text-primary aria-selected:text-primary"
103110
)}
104111
>
105112
<Show when={active() === "preview"}>
106113
{/* // Motion.div */}
107114
<div
108-
class="prefers-dark:!bg-white/15 -z-10 absolute inset-0 size-full bg-white shadow-sm dark:bg-white/25"
115+
class="-z-10 absolute inset-0 size-full rounded-full bg-white/25 shadow-sm"
109116
style={{ "border-radius": "999" }}
110117
// layout
111118
// layoutId={`${id}active`}
@@ -117,13 +124,13 @@ function Demo(props: FlowProps<Props>) {
117124
value="code"
118125
class={clsx(
119126
active() !== "code" && "hover:transition-[color]",
120-
"relative px-2 py-1 font-medium text-xs/4 text-zinc-600 hover:text-primary aria-selected:text-primary dark:text-muted"
127+
"relative px-2 py-1 font-medium text-muted text-xs/4 hover:text-primary aria-selected:text-primary"
121128
)}
122129
>
123130
<Show when={active() === "code"}>
124131
{/* // Motion.div */}
125132
<div
126-
class="prefers-dark:!bg-white/15 -z-10 absolute inset-0 size-full bg-white shadow-sm dark:bg-white/25"
133+
class="-z-10 absolute inset-0 size-full rounded-full bg-white/25 shadow-sm"
127134
style={{ "border-radius": "999" }}
128135
// layout
129136
// layoutId={`${id}active`}
@@ -134,43 +141,86 @@ function Demo(props: FlowProps<Props>) {
134141
</Tabs.List>
135142
{/* </MotionConfig> */}
136143
</Show>
137-
<Tabs.Content
138-
value="preview"
139-
class={clsx(
140-
_props.class,
141-
"relative rounded-lg border border-faint data-[state=inactive]:hidden"
142-
)}
144+
<Collapsible
145+
open={true}
146+
containerClass={cn("rounded-lg border border-faint", active() === "code" && "bg-[#18181B]")}
143147
>
144-
<Show when={renderedTitle()}>
145-
<div class="absolute top-3 left-3">{renderedTitle()}</div>
146-
</Show>
148+
<Show when={active() === "preview"}>
149+
<Tabs.Content value="preview" class={clsx(_props.class, "relative")}>
150+
<Show when={renderedTitle()}>
151+
<div class="absolute top-3 left-3">{renderedTitle()}</div>
152+
</Show>
147153

148-
<div
149-
class={clsx(_props.minHeight, "flex flex-col items-center justify-center p-5 pb-6")}
150-
onClick={handleClick}
151-
onMouseDown={handleMouseDown}
152-
>
153-
{_props.children}
154-
{_props?.onClick && (
155-
<span
156-
class={clsx(
157-
"absolute bottom-5 left-0 w-full text-center text-sm text-zinc-400 transition-opacity duration-200 ease-out",
158-
knowsToClick() && "opacity-0"
154+
<div
155+
class={clsx(_props.minHeight, "flex flex-col items-center justify-center p-5 pb-6")}
156+
onClick={handleClick}
157+
onMouseDown={handleMouseDown}
158+
>
159+
{_props.children}
160+
{_props?.onClick && (
161+
<span
162+
class={clsx(
163+
"absolute bottom-5 left-0 w-full text-center text-sm text-zinc-400 transition-opacity duration-200 ease-out",
164+
knowsToClick() && "opacity-0"
165+
)}
166+
>
167+
Click anywhere to change numbers
168+
</span>
159169
)}
170+
</div>
171+
</Tabs.Content>
172+
</Show>
173+
<Show when={active() === "code" && _props.code}>
174+
<Tabs.Content value="code" class="relative overflow-hidden p-3 text-sm">
175+
<MDXContent code={_props.code!} />
176+
<button
177+
onClick={() => {
178+
const code = _props.code || ""
179+
const trimmedCode = code
180+
.replace(/^\s*```tsx\s*\n?/, "") // Remove ```tsx from beginning
181+
.replace(/\n?\s*```\s*$/, "") // Remove ``` from end
182+
copy(trimmedCode)
183+
}}
184+
class="absolute right-2 bottom-2 animate-fadeIn rounded-md bg-white/10 p-1 transition-colors hover:bg-white/20"
160185
>
161-
Click anywhere to change numbers
162-
</span>
163-
)}
164-
</div>
165-
</Tabs.Content>
166-
<Show when={_props.code}>
167-
<Tabs.Content
168-
value="code"
169-
class="relative overflow-hidden rounded-lg border border-faint bg-zinc-950 text-sm"
170-
>
171-
{_props.code}
172-
</Tabs.Content>
173-
</Show>
186+
<Show
187+
when={!copied()}
188+
fallback={
189+
<svg
190+
class="h-4 w-4 animate-scale-in text-white"
191+
fill="none"
192+
stroke="currentColor"
193+
viewBox="0 0 24 24"
194+
xmlns="http://www.w3.org/2000/svg"
195+
>
196+
<path
197+
stroke-linecap="round"
198+
stroke-linejoin="round"
199+
stroke-width="2"
200+
d="M5 13l4 4L19 7"
201+
/>
202+
</svg>
203+
}
204+
>
205+
<svg
206+
class="h-4 w-4 animate-scale-in text-white"
207+
fill="none"
208+
stroke="currentColor"
209+
viewBox="0 0 24 24"
210+
xmlns="http://www.w3.org/2000/svg"
211+
>
212+
<path
213+
stroke-linecap="round"
214+
stroke-linejoin="round"
215+
stroke-width="2"
216+
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
217+
></path>
218+
</svg>
219+
</Show>
220+
</button>
221+
</Tabs.Content>
222+
</Show>
223+
</Collapsible>
174224
</Tabs>
175225
)
176226
}
@@ -254,12 +304,125 @@ export function DemoSwitch(props: ComponentProps<typeof Switch>) {
254304
<Switch.Control
255305
class={clsx(
256306
props.class,
257-
"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"
307+
"group relative flex h-6 w-11 cursor-pointer rounded-full bg-zinc-800 p-0.5 transition-colors duration-200 ease-in-out focus:outline-none data-checked:bg-zinc-50 data-focus:outline-2 data-focus:outline-blue-500"
258308
)}
259309
>
260-
<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" />
310+
<Switch.Thumb class="spring-bounce-0 spring-duration-200 pointer-events-none inline-block size-5 rounded-full bg-zinc-950 shadow-lg ring-0 transition-transform group-data-checked:translate-x-5" />
261311
</Switch.Control>
262312
<Switch.Label class="text-xs">{props.children as any}</Switch.Label>
263313
</Switch>
264314
)
265315
}
316+
317+
// NOT A SOLID-UI / SHADCN COMPONENT. CUSTOM BY CARLO
318+
// A11Y NOTES ----------------------------------------------------
319+
// - The extra divs here (outer animating wrapper + inner measure div)
320+
// are **not** exposed to the a11y tree because:
321+
// – we don't add any semantic roles
322+
// – we don't add any labelling attributes
323+
// So screen-readers will only “see” the real interactive controls
324+
// that compose this component (buttons, links, inputs, etc.).
325+
// Multiple layout divs are therefore “noise-free”.
326+
// - The collapsing action itself should be announced by whatever
327+
// trigger toggles the `open` prop (e.g. the parent button should
328+
// have `aria-expanded` and `aria-controls`).
329+
// - If this component is ever used as a disclosure region directly,
330+
// add `id`, `role="region"` and `aria-labelledby` (or `aria-label`)
331+
// on the outer div so assistive tech can associate it with the
332+
// controlling button.
333+
// - `overflow:hidden` on the outer div prevents keyboard focus from
334+
// landing on off-screen descendants when closed, but double-check
335+
// that no forced-focus logic circumvents that.
336+
// - Prefer `prefers-reduced-motion` in consumer code to disable
337+
// the `transition-all` class when the user has requested it.
338+
// - HIDING CONTENT FROM SR WHEN CLOSED: Yes, this is the usual,
339+
// expected pattern. When a disclosure widget is closed we remove
340+
// its descendants from the a11y tree with `aria-hidden` so users
341+
// cannot read/traverse it. (aria-expanded=false already implies
342+
// invisibility for SR users; aria-hidden just makes it official.)
343+
// When open we drop aria-hidden so the region is discoverable again.
344+
// - External trigger a11y docs - added.
345+
// ----------------------------------------------------------------
346+
347+
export interface CollapsibleProps extends JSX.HTMLAttributes<HTMLDivElement> {
348+
open?: boolean
349+
/**
350+
* Applied to the collapsing wrapper div (the element whose height changes). Generally no need to touch this besides changing the transition duration.
351+
*/
352+
containerClass?: string
353+
/**
354+
* Applied to the content div (the measured inner wrapper)
355+
*/
356+
class?: string
357+
children: JSX.Element
358+
}
359+
360+
/*
361+
* Fluid height collapsible built on native resize events.
362+
* Uses transition-* utilities (duration/ease) – the root parent gets
363+
* a changing style.height that the utility class animates.
364+
* Works exactly like an Accordion panel but without any trigger
365+
* baggage; state is 100% parent-controlled via open={bool}.
366+
*
367+
* A11y Notes for external triggers (Just a minor trade-off from not using an Accordion component).
368+
* If an external button/link controls this panel you should:
369+
* 1. Pass a unique `id` to Collapsible (e.g. id="faq-panel-3").
370+
* 2. On the trigger element, add:
371+
* aria-expanded={open}
372+
* aria-controls="faq-panel-3"
373+
* where `open` is the same boolean you pass to Collapsible.
374+
* 3. Optionally set the `role="region"` prop on Collapsible so
375+
* screen-reader users hear "region" when entering the panel.
376+
* 4. If you want a visible label, add `aria-labelledby`
377+
* to the trigger button (not Collapsible) pointing
378+
* to the ID of the heading that names the panel.
379+
* Example:
380+
* <h3 id="faq-title">Shipping options</h3>
381+
* <button aria-expanded={open} aria-controls="faq-panel-3" aria-labelledby="faq-title">
382+
* …
383+
* </button>
384+
*/
385+
export function Collapsible(props: CollapsibleProps) {
386+
let innerRef: HTMLDivElement | undefined
387+
let lastHeight = 0
388+
389+
const [local, others] = splitProps(props, ["open", "containerClass", "class", "children"])
390+
const [height, setHeight] = createSignal<number | string>("auto")
391+
392+
// Observe the *inner* element’s size so any content change is reflected
393+
const resizeHandler = () => {
394+
if (innerRef) {
395+
lastHeight = innerRef.scrollHeight
396+
setHeight(lastHeight) // keep latest value when open
397+
}
398+
}
399+
400+
let ro: ResizeObserver
401+
onMount(() => {
402+
if (!innerRef) return
403+
ro = new ResizeObserver(resizeHandler)
404+
ro.observe(innerRef)
405+
resizeHandler() // ensure initial read
406+
})
407+
408+
onCleanup(() => ro?.disconnect())
409+
410+
// Select what to render based on open state
411+
// (invoke children here once so ResizeObserver sees static children of inner)
412+
const content = children(() => local.children)
413+
414+
const heightStyle = () => (local.open ? `${height()}px` : "0px")
415+
return (
416+
<div
417+
style={{ height: heightStyle() }}
418+
class={cn("overflow-hidden transition-[width,height] duration-400", local.containerClass)}
419+
aria-hidden={!local.open}
420+
{...others}
421+
>
422+
{/* inner wrapper we measure */}
423+
<div ref={innerRef} class={cn(local.class, "w-full")}>
424+
{content()}
425+
</div>
426+
</div>
427+
)
428+
}

dev/components/demos/continuous.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import Demo, { type DemoProps, DemoSwitch } from "dev/components/demos/base-demo"
2-
import { MDXContent } from "dev/components/mdx-content"
32
import { useCycle } from "dev/hooks/use-cycle"
43
import { continuous } from "number-flow/plugins"
54
import { createSignal } from "solid-js"
@@ -13,7 +12,7 @@ export default function ContinuousDemo(props: Omit<DemoProps, "children" | "code
1312

1413
const code = `
1514
\`\`\`tsx
16-
import NumberFlow, { continuous } from '@number-flow/solid'
15+
import NumberFlow, { continuous } from 'solid-number-flow'
1716
1817
<NumberFlow
1918
plugins={[continuous]}
@@ -25,7 +24,7 @@ import NumberFlow, { continuous } from '@number-flow/solid'
2524
return (
2625
<Demo
2726
{...props}
28-
code={<MDXContent code={code} />}
27+
code={code}
2928
title={
3029
<DemoSwitch checked={isContinuous()} onChange={setContinuous}>
3130
<code class="font-semibold">continuous</code>

dev/components/demos/isolate.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import Demo, { DemoSwitch, type DemoProps } from "dev/components/demos/base-demo"
2-
import { MDXContent } from "dev/components/mdx-content"
32
import { createSignal } from "solid-js"
43
import NumberFlow from "src"
54

@@ -20,7 +19,7 @@ export default function IsolateDemo(props: Omit<DemoProps, "children" | "code">)
2019
return (
2120
<Demo
2221
{...props}
23-
code={<MDXContent code={code} />}
22+
code={code}
2423
title={
2524
<DemoSwitch checked={isolate()} onChange={setIsolate}>
2625
<code class="font-semibold">isolate</code>
@@ -29,7 +28,7 @@ export default function IsolateDemo(props: Omit<DemoProps, "children" | "code">)
2928
onClick={() => setIncreased((o) => !o)}
3029
>
3130
<div class="~text-3xl/4xl flex items-center gap-4">
32-
{increased() && <div class="bg-zinc-200 dark:bg-zinc-800 ~w-20/40 h-[1em] rounded-sm" />}
31+
{increased() && <div class="bg-zinc-800 ~w-20/40 h-[1em] rounded-sm" />}
3332
<NumberFlow
3433
locales="en-US"
3534
isolate={isolate()}

dev/components/demos/styling.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import Demo, { type DemoProps } from "dev/components/demos/base-demo"
2-
import { MDXContent } from "dev/components/mdx-content"
32
import { useCycle } from "dev/hooks/use-cycle"
43
import NumberFlow, { type Value } from "src"
54

@@ -20,7 +19,7 @@ number-flow::part(suffix) {
2019
`
2120

2221
return (
23-
<Demo {...props} code={<MDXContent code={code} />} onClick={cycleValue}>
22+
<Demo {...props} code={code} onClick={cycleValue}>
2423
<NumberFlow
2524
locales="en-US"
2625
value={value()}

0 commit comments

Comments
 (0)