Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 470
feat(swingset): single-page overviews with inline playground#8818
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
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| --- | ||
| --- |
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| 'use client'; | ||
| import { Input } from '@/components/ui/input'; | ||
| import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; | ||
| import { Switch } from '@/components/ui/switch'; | ||
| import type { KnobDef, KnobValues } from '@/lib/types'; | ||
| interface KnobControlProps { | ||
| id: string; | ||
| def: KnobDef; | ||
| value: KnobValues[string]; | ||
| onChange: (value: KnobValues[string]) => void; | ||
| } | ||
| /** A single auto-generated control for one knob, used inside the interactive prop table. */ | ||
| export function KnobControl({ id, def, value, onChange }: KnobControlProps) { | ||
| switch (def.type) { | ||
| case 'boolean': | ||
| return ( | ||
| <Switch | ||
| id={id} | ||
| checked={value as boolean} | ||
| onCheckedChange={checked => onChange(checked === true)} | ||
| /> | ||
| ); | ||
| case 'text': | ||
| return ( | ||
| <Input | ||
| id={id} | ||
| value={value as string} | ||
| onChange={e => onChange(e.target.value)} | ||
| className='h-7 text-xs' | ||
| /> | ||
| ); | ||
| case 'number': | ||
| return ( | ||
| <Input | ||
| id={id} | ||
| type='number' | ||
| value={value as number} | ||
| min={def.min} | ||
| max={def.max} | ||
| step={def.step} | ||
| onChange={e => onChange(e.target.valueAsNumber)} | ||
| className='h-7 w-24 text-xs' | ||
| /> | ||
| ); | ||
| case 'select': { | ||
| const items = def.options.map(opt => ({ label: opt, value: opt })); | ||
| return ( | ||
| <Select | ||
| items={items} | ||
| value={value as string} | ||
| onValueChange={v => onChange(v ?? '')} | ||
| > | ||
| <SelectTrigger className='h-7 w-full min-w-32 text-xs'> | ||
| <SelectValue /> | ||
| </SelectTrigger> | ||
| <SelectContent> | ||
| <SelectGroup> | ||
| {items.map(item => ( | ||
| <SelectItem | ||
| key={item.value} | ||
| value={item.value} | ||
| > | ||
| {item.label} | ||
| </SelectItem> | ||
| ))} | ||
| </SelectGroup> | ||
| </SelectContent> | ||
| </Select> | ||
| ); | ||
| } | ||
| } | ||
| } | ||
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| 'use client'; | ||
| import type { MosaicVariables } from '@clerk/ui/mosaic/variables'; | ||
| import type React from 'react'; | ||
| import { createContext, useContext, useMemo, useState } from 'react'; | ||
| import { generateKnobs, initKnobValues } from '@/lib/generateKnobs'; | ||
| import type { KnobRecord, KnobValues, StoryMeta } from '@/lib/types'; | ||
| interface PlaygroundContextValue { | ||
| /** Knob definitions derived from the component's CVA `_variants`. */ | ||
| knobs: KnobRecord; | ||
| /** Current value for each knob (props passed into the live preview). */ | ||
| values: KnobValues; | ||
| setValue: (key: string, value: KnobValues[string]) => void; | ||
| /** Live Mosaic design-token overrides applied via `MosaicProvider`. */ | ||
| variables: MosaicVariables; | ||
| setVariables: (variables: MosaicVariables) => void; | ||
| /** Restore every knob to its default and clear variable overrides. */ | ||
| reset: () => void; | ||
| } | ||
| const PlaygroundContext = createContext<PlaygroundContextValue | null>(null); | ||
| /** | ||
| * Holds the shared playground state for a single component's overview page. The | ||
| * `<Preview>` reads it to render the live component while `<PropTable>` renders the | ||
| * matching interactive controls, so editing a prop's value updates the preview. | ||
| */ | ||
| export function PlaygroundProvider({ meta, children }: { meta?: StoryMeta; children: React.ReactNode }) { | ||
| const knobs = useMemo(() => (meta ? generateKnobs(meta) : {}), [meta]); | ||
| const [values, setValues] = useState<KnobValues>(() => initKnobValues(knobs)); | ||
| const [variables, setVariables] = useState<MosaicVariables>({}); | ||
| const value = useMemo<PlaygroundContextValue>( | ||
| () => ({ | ||
| knobs, | ||
| values, | ||
| setValue: (key, v) => setValues(prev => ({ ...prev, [key]: v })), | ||
| variables, | ||
| setVariables, | ||
| reset: () => { | ||
| setValues(initKnobValues(knobs)); | ||
| setVariables({}); | ||
| }, | ||
| }), | ||
| [knobs, values, variables], | ||
| ); | ||
| return <PlaygroundContext.Provider value={value}>{children}</PlaygroundContext.Provider>; | ||
| } | ||
| export function usePlayground(): PlaygroundContextValue | null { | ||
| return useContext(PlaygroundContext); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against
NaNfrom number input.e.target.valueAsNumberreturnsNaNwhen the user types invalid input (e.g.,"abc"). ThisNaNwould propagate toplayground.valuesand break the live preview. Validate the input or fallback to the current value when invalid.🛡️ Proposed fix
onChange={e => onChange(e.target.valueAsNumber)} + onChange={e => {+ const num = e.target.valueAsNumber;+ if (!Number.isNaN(num)) {+ onChange(num);+ }+ }}📝 Committable suggestion
🤖 Prompt for AI Agents