@krizaka/ui 2.0.0-beta.6 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -10
- package/dist/{button-D5aSsmxX.d.ts → button-Cgfkg_3y.d.ts} +5 -1
- package/dist/button.d.ts +5 -1
- package/dist/button.js +4 -2
- package/dist/button.js.map +1 -1
- package/dist/card.d.ts +7 -1
- package/dist/card.js +2 -2
- package/dist/card.js.map +1 -1
- package/dist/{chunk-IUGBKMJC.js → chunk-FHYZSO2S.js} +2 -2
- package/dist/{chunk-6DJ6SQY3.js → chunk-PESA2R4U.js} +5 -3
- package/dist/chunk-PESA2R4U.js.map +1 -0
- package/dist/command.d.ts +11 -2
- package/dist/command.js +17 -6
- package/dist/command.js.map +1 -1
- package/dist/confirm-button.d.ts +1 -1
- package/dist/confirm-button.js +1 -1
- package/dist/countdown.d.ts +7 -6
- package/dist/countdown.js +12 -8
- package/dist/countdown.js.map +1 -1
- package/dist/dialog.js +2 -2
- package/dist/dropdown-menu.js +3 -3
- package/dist/dropdown-menu.js.map +1 -1
- package/dist/field.js +2 -2
- package/dist/field.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +52 -10
- package/dist/index.js.map +1 -1
- package/dist/native.d.ts +20 -8
- package/dist/native.js +170 -184
- package/dist/native.js.map +1 -1
- package/dist/theme.d.ts +19 -9
- package/dist/theme.js +8 -5
- package/dist/theme.js.map +1 -1
- package/package.json +3 -3
- package/registry/avatar.json +20 -5
- package/registry/button.json +36 -15
- package/registry/card.json +17 -2
- package/registry/command.json +30 -8
- package/registry/confirm-button.json +4 -4
- package/registry/countdown.json +53 -12
- package/registry/dropdown-menu.json +1 -1
- package/registry/examples/button/gradient.d.ts +4 -0
- package/registry/examples/button/gradient.js +11 -0
- package/registry/examples/button/gradient.tsx +5 -0
- package/registry/examples/button/native/gradient.d.ts +4 -0
- package/registry/examples/button/native/gradient.dark.png +0 -0
- package/registry/examples/button/native/gradient.js +12 -0
- package/registry/examples/button/native/gradient.light.png +0 -0
- package/registry/examples/button/native/gradient.tsx +5 -0
- package/registry/examples/command/dialog.js +1 -2
- package/registry/examples/command/dialog.tsx +1 -2
- package/registry/examples/command/inline.js +1 -2
- package/registry/examples/command/inline.tsx +1 -2
- package/registry/examples/command/no-results.d.ts +4 -0
- package/registry/examples/command/no-results.js +19 -0
- package/registry/examples/command/no-results.tsx +20 -0
- package/registry/examples/countdown/native/urgent.dark.png +0 -0
- package/registry/examples/countdown/native/urgent.light.png +0 -0
- package/registry/examples/countdown/native/with-label.d.ts +4 -0
- package/registry/examples/countdown/native/with-label.dark.png +0 -0
- package/registry/examples/countdown/native/with-label.js +13 -0
- package/registry/examples/countdown/native/with-label.light.png +0 -0
- package/registry/examples/countdown/native/with-label.tsx +7 -0
- package/registry/examples/countdown/with-label.d.ts +4 -0
- package/registry/examples/countdown/with-label.js +13 -0
- package/registry/examples/countdown/with-label.tsx +10 -0
- package/registry/field.json +1 -1
- package/registry/index.json +8 -8
- package/registry/theme.json +24 -9
- package/dist/chunk-6DJ6SQY3.js.map +0 -1
- /package/dist/{chunk-IUGBKMJC.js.map → chunk-FHYZSO2S.js.map} +0 -0
package/registry/command.json
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"bestPractices": [
|
|
29
29
|
"Group the commands (`Command.Group heading`) and show their shortcuts with `Command.Shortcut`.",
|
|
30
30
|
"Add `keywords` so a command is found by the words people type, not only by its label.",
|
|
31
|
-
"Always say what an empty search means with `Command.
|
|
31
|
+
"Always say what an empty search means with `emptyLabel` on `Command.List` (said beside the listbox, never inside it)."
|
|
32
32
|
],
|
|
33
33
|
"accessibility": {
|
|
34
34
|
"keyboard": [
|
|
@@ -97,6 +97,13 @@
|
|
|
97
97
|
"component": "CommandList",
|
|
98
98
|
"description": "",
|
|
99
99
|
"props": [
|
|
100
|
+
{
|
|
101
|
+
"name": "emptyLabel",
|
|
102
|
+
"type": "ReactNode",
|
|
103
|
+
"default": null,
|
|
104
|
+
"description": "Shown when nothing matches — passed translated. Rendered after the listbox, never inside it: an empty listbox is\nvalid, a listbox holding text is not.",
|
|
105
|
+
"required": false
|
|
106
|
+
},
|
|
100
107
|
{
|
|
101
108
|
"name": "label",
|
|
102
109
|
"type": "string",
|
|
@@ -108,7 +115,7 @@
|
|
|
108
115
|
},
|
|
109
116
|
{
|
|
110
117
|
"component": "CommandEmpty",
|
|
111
|
-
"description": "Shown by cmdk only when no item matches.",
|
|
118
|
+
"description": "Shown by cmdk only when no item matches. Place it beside `Command.List` (or pass `emptyLabel` to the list), never\ninside it: inside, it is rendered as before but warns in development — the listbox then holds text, which axe\nrefuses (`aria-required-children`).",
|
|
112
119
|
"props": [
|
|
113
120
|
{
|
|
114
121
|
"name": "emptyLabel",
|
|
@@ -200,7 +207,15 @@
|
|
|
200
207
|
"description": "In a card: the input, groups, shortcuts, a disabled item.",
|
|
201
208
|
"path": "examples/command/inline.tsx",
|
|
202
209
|
"module": "@krizaka/ui/registry/examples/command/inline",
|
|
203
|
-
"code": "import { Command } from \"@krizaka/ui/command\";\n\nexport default function CommandInline() {\n return (\n <Command.Root label=\"Commands\" className=\"w-[28rem] max-w-full rounded-xl border border-border-default shadow-lg\">\n <Command.Input placeholder=\"Type a command or search\" />\n <Command.List label=\"Suggestions\"
|
|
210
|
+
"code": "import { Command } from \"@krizaka/ui/command\";\n\nexport default function CommandInline() {\n return (\n <Command.Root label=\"Commands\" className=\"w-[28rem] max-w-full rounded-xl border border-border-default shadow-lg\">\n <Command.Input placeholder=\"Type a command or search\" />\n <Command.List label=\"Suggestions\" emptyLabel=\"Nothing matches.\">\n <Command.Group heading=\"Pages\">\n <Command.Item>\n Home\n <Command.Shortcut>G H</Command.Shortcut>\n </Command.Item>\n <Command.Item>\n Wallet\n <Command.Shortcut>G W</Command.Shortcut>\n </Command.Item>\n <Command.Item disabled>Studio</Command.Item>\n </Command.Group>\n <Command.Separator />\n <Command.Group heading=\"Actions\">\n <Command.Item keywords={[\"video\", \"new\"]}>Upload a video</Command.Item>\n <Command.Item>Switch the theme</Command.Item>\n </Command.Group>\n </Command.List>\n </Command.Root>\n );\n}\n"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"name": "no-results",
|
|
214
|
+
"title": "No results",
|
|
215
|
+
"description": "`emptyLabel` on `Command.List`: said beside the listbox when nothing matches.",
|
|
216
|
+
"path": "examples/command/no-results.tsx",
|
|
217
|
+
"module": "@krizaka/ui/registry/examples/command/no-results",
|
|
218
|
+
"code": "\"use client\";\n\nimport { Command } from \"@krizaka/ui/command\";\nimport { useState } from \"react\";\n\n// Nothing matches: `emptyLabel` is said beside the results, the listbox stays empty (valid for screen readers).\nexport default function CommandNoResults() {\n const [query, setQuery] = useState(\"zzz\");\n return (\n <Command.Root label=\"Commands\" className=\"w-[28rem] max-w-full rounded-xl border border-border-default shadow-lg\">\n <Command.Input placeholder=\"Type a command or search\" value={query} onValueChange={setQuery} />\n <Command.List label=\"Suggestions\" emptyLabel={`Nothing matches “${query}”.`}>\n <Command.Group heading=\"Pages\">\n <Command.Item>Home</Command.Item>\n <Command.Item>Wallet</Command.Item>\n </Command.Group>\n </Command.List>\n </Command.Root>\n );\n}\n"
|
|
204
219
|
},
|
|
205
220
|
{
|
|
206
221
|
"name": "dialog",
|
|
@@ -208,7 +223,7 @@
|
|
|
208
223
|
"description": "`CommandDialog`: the palette of an app, opened from a button or a shortcut.",
|
|
209
224
|
"path": "examples/command/dialog.tsx",
|
|
210
225
|
"module": "@krizaka/ui/registry/examples/command/dialog",
|
|
211
|
-
"code": "\"use client\";\n\nimport { Button } from \"@krizaka/ui/button\";\nimport { Command, CommandDialog } from \"@krizaka/ui/command\";\nimport { Kbd } from \"@krizaka/ui/kbd\";\nimport { useState } from \"react\";\n\n// The palette of an app, in the platform's Dialog: open it from a button or your own ⌘K shortcut.\nexport default function CommandDialogExample({ defaultOpen = false }: { defaultOpen?: boolean }) {\n const [open, setOpen] = useState(defaultOpen);\n return (\n <>\n <Button variant=\"outline\" onClick={() => setOpen(true)}>\n Search <Kbd size=\"sm\">⌘K</Kbd>\n </Button>\n <CommandDialog\n open={open}\n onOpenChange={setOpen}\n label=\"Search\"\n footer={<p className=\"border-t border-border-subtle bg-surface-2 px-4 py-2.5 text-xs text-fg-secondary\">↑↓ to move, ↵ to open</p>}\n >\n <Command.Input placeholder=\"Search creators, videos, tags\" />\n <Command.List label=\"Results\"
|
|
226
|
+
"code": "\"use client\";\n\nimport { Button } from \"@krizaka/ui/button\";\nimport { Command, CommandDialog } from \"@krizaka/ui/command\";\nimport { Kbd } from \"@krizaka/ui/kbd\";\nimport { useState } from \"react\";\n\n// The palette of an app, in the platform's Dialog: open it from a button or your own ⌘K shortcut.\nexport default function CommandDialogExample({ defaultOpen = false }: { defaultOpen?: boolean }) {\n const [open, setOpen] = useState(defaultOpen);\n return (\n <>\n <Button variant=\"outline\" onClick={() => setOpen(true)}>\n Search <Kbd size=\"sm\">⌘K</Kbd>\n </Button>\n <CommandDialog\n open={open}\n onOpenChange={setOpen}\n label=\"Search\"\n footer={<p className=\"border-t border-border-subtle bg-surface-2 px-4 py-2.5 text-xs text-fg-secondary\">↑↓ to move, ↵ to open</p>}\n >\n <Command.Input placeholder=\"Search creators, videos, tags\" />\n <Command.List label=\"Results\" emptyLabel=\"Nothing matches.\">\n <Command.Group heading=\"Pages\">\n <Command.Item onSelect={() => setOpen(false)}>Home</Command.Item>\n <Command.Item onSelect={() => setOpen(false)}>Wallet</Command.Item>\n </Command.Group>\n <Command.Group heading=\"Actions\">\n <Command.Item keywords={[\"video\", \"new\"]}>Upload a video</Command.Item>\n </Command.Group>\n </Command.List>\n </CommandDialog>\n </>\n );\n}\n"
|
|
212
227
|
}
|
|
213
228
|
]
|
|
214
229
|
},
|
|
@@ -216,7 +231,7 @@
|
|
|
216
231
|
"files": [
|
|
217
232
|
{
|
|
218
233
|
"path": "command/command.tsx",
|
|
219
|
-
"content": "// Client. A command palette on cmdk: the combobox / listbox roles, the active option (aria-activedescendant), the\n// arrows, Home / End, Enter, and the filtering (turn it off with `shouldFilter={false}` when the server searches).\n// `CommandDialog` puts it in the platform's Dialog. Words arrive as props.\nimport { Command as CommandPrimitive } from \"cmdk\";\nimport { Dialog as DialogPrimitive } from \"radix-ui\";\nimport
|
|
234
|
+
"content": "// Client. A command palette on cmdk: the combobox / listbox roles, the active option (aria-activedescendant), the\n// arrows, Home / End, Enter, and the filtering (turn it off with `shouldFilter={false}` when the server searches).\n// `CommandDialog` puts it in the platform's Dialog. Words arrive as props.\nimport { Command as CommandPrimitive } from \"cmdk\";\nimport { Dialog as DialogPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { cn } from \"../cn\";\nimport { DialogContent, type DialogContentProps } from \"../dialog/dialog\";\n\nexport const command = tv({\n slots: {\n root: \"flex h-full w-full flex-col overflow-hidden bg-surface-1 text-fg\",\n inputWrap: \"flex items-center gap-3 border-b border-border-default px-4\",\n icon: \"h-5 w-5 shrink-0 text-fg-secondary\",\n input:\n \"h-13 w-full min-w-0 flex-1 bg-transparent text-sm text-fg outline-hidden placeholder:text-fg-muted disabled:cursor-not-allowed disabled:opacity-50 sm:text-base\",\n list: \"max-h-[min(60dvh,26rem)] scroll-py-2 overflow-y-auto overscroll-contain p-2 [&:not(:has([cmdk-item]))]:p-0\",\n empty: \"px-3 py-8 text-center text-sm text-fg-secondary\",\n loading: \"px-3 py-6 text-center text-sm text-fg-secondary\",\n group:\n \"[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:pb-1.5 [&_[cmdk-group-heading]]:pt-3 [&_[cmdk-group-heading]]:text-xs \" +\n \"[&_[cmdk-group-heading]]:font-semibold [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-wider [&_[cmdk-group-heading]]:text-fg-secondary\",\n item:\n \"relative flex cursor-pointer select-none items-center gap-3 rounded-lg px-3 py-2.5 text-sm text-fg outline-hidden transition-colors \" +\n \"before:absolute before:inset-y-2 before:left-0 before:w-0.5 before:rounded-full before:bg-transparent \" +\n \"data-[selected=true]:bg-surface-2 data-[selected=true]:before:bg-accent data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-40\",\n separator: \"-mx-2 my-1 h-px bg-border-subtle\",\n shortcut: \"ml-auto text-xs tracking-widest text-fg-secondary\",\n },\n});\nconst s = command();\n\nfunction SearchIcon({ className }: { className?: string }) {\n return (\n <svg viewBox=\"0 0 24 24\" className={className} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"m20 20-3.5-3.5\" />\n </svg>\n );\n}\n\nexport type CommandRootProps = React.ComponentProps<typeof CommandPrimitive> & {\n /** The accessible name of the palette — passed translated (\"Search\", \"Commands\"). */\n label: string;\n};\n\n/** The palette: `value` / `onValueChange` (the active item), `shouldFilter`, `filter`, `loop`. */\nexport function CommandRoot({ className, ...props }: CommandRootProps) {\n return <CommandPrimitive className={s.root({ className })} {...props} />;\n}\n\nexport type CommandInputProps = Omit<React.ComponentProps<typeof CommandPrimitive.Input>, \"placeholder\"> & {\n /** What to type — passed translated. The input is named by the palette's `label`. */\n placeholder: string;\n /** After the input, inside the bar: a spinner, a clear button. */\n trailing?: React.ReactNode;\n};\n\n/** The search field (`value` / `onValueChange` for the query), with a magnifier. */\nexport function CommandInput({ className, placeholder, trailing, ...props }: CommandInputProps) {\n return (\n <div className={s.inputWrap()} cmdk-input-wrapper=\"\">\n <SearchIcon className={s.icon()} />\n <CommandPrimitive.Input placeholder={placeholder} className={s.input({ className })} {...props} />\n {trailing}\n </div>\n );\n}\n\n// Set inside `Command.List`: a listbox may only hold options and groups (axe `aria-required-children`), so the empty\n// message must live beside it, not in it.\nconst InsideList = React.createContext(false);\n\nexport type CommandListProps = React.ComponentProps<typeof CommandPrimitive.List> & {\n /** The accessible name of the list of results — passed translated (\"Suggestions\", \"Results\"). */\n label: string;\n /**\n * Shown when nothing matches — passed translated. Rendered after the listbox, never inside it: an empty listbox is\n * valid, a listbox holding text is not.\n */\n emptyLabel?: React.ReactNode;\n};\n\nexport function CommandList({ className, emptyLabel, children, ...props }: CommandListProps) {\n return (\n <>\n <CommandPrimitive.List className={s.list({ className })} {...props}>\n <InsideList.Provider value>{children}</InsideList.Provider>\n </CommandPrimitive.List>\n {emptyLabel === undefined ? null : <CommandEmpty emptyLabel={emptyLabel} />}\n </>\n );\n}\n\nexport type CommandEmptyProps = Omit<React.ComponentProps<typeof CommandPrimitive.Empty>, \"children\"> & {\n /** Shown when nothing matches — passed translated. */\n emptyLabel: React.ReactNode;\n};\n\n/**\n * Shown by cmdk only when no item matches. Place it beside `Command.List` (or pass `emptyLabel` to the list), never\n * inside it: inside, it is rendered as before but warns in development — the listbox then holds text, which axe\n * refuses (`aria-required-children`).\n */\nexport function CommandEmpty({ className, emptyLabel, ...props }: CommandEmptyProps) {\n const inside = React.useContext(InsideList);\n React.useEffect(() => {\n if (inside && typeof process !== \"undefined\" && process.env.NODE_ENV !== \"production\") {\n console.warn(\"@krizaka/ui/command: <Command.Empty> inside <Command.List> breaks the listbox (axe aria-required-children). Pass `emptyLabel` to <Command.List> instead.\");\n }\n }, [inside]);\n return (\n <CommandPrimitive.Empty className={s.empty({ className })} {...props}>\n {emptyLabel}\n </CommandPrimitive.Empty>\n );\n}\n\n/** While results load (`label` is announced). */\nexport function CommandLoading({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Loading>) {\n return <CommandPrimitive.Loading className={s.loading({ className })} {...props} />;\n}\n\n/** A group of items under a `heading`. */\nexport function CommandGroup({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Group>) {\n return <CommandPrimitive.Group className={s.group({ className })} {...props} />;\n}\n\n/** An option: `onSelect` on Enter or click, `value` and `keywords` for the filter, `disabled`. */\nexport function CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) {\n return <CommandPrimitive.Item className={s.item({ className })} {...props} />;\n}\n\n/** A line between groups. Decorative (`role=\"none\"`): cmdk's own separator is a role a listbox may not hold (axe). */\nexport function CommandSeparator({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div role=\"none\" className={s.separator({ className })} {...props} />;\n}\n\n/** A keyboard hint at the end of an item. */\nexport function CommandShortcut({ className, ...props }: React.ComponentProps<\"span\">) {\n return <span className={s.shortcut({ className })} {...props} />;\n}\n\n/** `Command.Root/Input/List/Empty/Loading/Group/Item/Separator/Shortcut`. */\nexport const Command = {\n Root: CommandRoot,\n Input: CommandInput,\n List: CommandList,\n Empty: CommandEmpty,\n Loading: CommandLoading,\n Group: CommandGroup,\n Item: CommandItem,\n Separator: CommandSeparator,\n Shortcut: CommandShortcut,\n};\n\nexport type CommandDialogProps = Omit<CommandRootProps, \"title\"> & {\n /** Open or closed (controlled), with `onOpenChange`. */\n open?: boolean;\n /** Open at first, uncontrolled. */\n defaultOpen?: boolean;\n /** Called when it opens or closes (Escape, a click outside, a shortcut of the product). */\n onOpenChange?: (open: boolean) => void;\n /** Under the palette, inside the dialog: shortcuts, a \"see all results\" link. */\n footer?: React.ReactNode;\n /** Passed to `Dialog.Content`: `size`, `container`, `className` of the dialog. */\n contentProps?: Omit<Extract<DialogContentProps, { hideClose: true }>, \"hideClose\" | \"children\" | \"placement\">;\n};\n\n/**\n * The palette in a dialog (Radix: focus trap, Escape, focus return). The dialog is named by `label`; it has no close\n * button — Escape and a click outside close it — and the input takes the focus when it opens.\n */\nexport function CommandDialog({ open, defaultOpen, onOpenChange, footer, contentProps, label, className, children, ...props }: CommandDialogProps) {\n const { className: contentClassName, ...content } = contentProps ?? {};\n return (\n <DialogPrimitive.Root open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>\n <DialogContent\n hideClose\n size=\"lg\"\n aria-describedby={undefined}\n className={cn(\"top-[12dvh] translate-y-0 sm:top-[14dvh]\", contentClassName)}\n {...content}\n >\n <DialogPrimitive.Title className=\"sr-only\">{label}</DialogPrimitive.Title>\n <CommandRoot label={label} className={className} {...props}>\n {children}\n </CommandRoot>\n {footer}\n </DialogContent>\n </DialogPrimitive.Root>\n );\n}\n"
|
|
220
235
|
},
|
|
221
236
|
{
|
|
222
237
|
"path": "command/index.ts",
|
|
@@ -224,7 +239,7 @@
|
|
|
224
239
|
},
|
|
225
240
|
{
|
|
226
241
|
"path": "command/meta.ts",
|
|
227
|
-
"content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Command\",\n summary: \"A command palette on cmdk: a search input over a filtered list of commands, inline or in a dialog.\",\n status: \"beta\",\n category: \"navigation\",\n platforms: \"web\",\n whenToUse: [\n \"To reach any page or action of an app from the keyboard (⌘K): `CommandDialog`.\",\n \"For a searchable list of choices inside a panel: `Command.Root` inline.\",\n \"When results come from the server: `shouldFilter={false}` and `Command.Loading`.\",\n ],\n whenNotToUse: [\n { when: \"For a short list of actions on one item.\", use: \"dropdown-menu\" },\n { when: \"For a choice among a few options in a form: use a select or radios.\", use: \"radio-group\" },\n { when: \"As the only way to navigate: keep the visible navigation.\" },\n ],\n bestPractices: [\n \"Group the commands (`Command.Group heading`) and show their shortcuts with `Command.Shortcut`.\",\n \"Add `keywords` so a command is found by the words people type, not only by its label.\",\n \"Always say what an empty search means with `Command.
|
|
242
|
+
"content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Command\",\n summary: \"A command palette on cmdk: a search input over a filtered list of commands, inline or in a dialog.\",\n status: \"beta\",\n category: \"navigation\",\n platforms: \"web\",\n whenToUse: [\n \"To reach any page or action of an app from the keyboard (⌘K): `CommandDialog`.\",\n \"For a searchable list of choices inside a panel: `Command.Root` inline.\",\n \"When results come from the server: `shouldFilter={false}` and `Command.Loading`.\",\n ],\n whenNotToUse: [\n { when: \"For a short list of actions on one item.\", use: \"dropdown-menu\" },\n { when: \"For a choice among a few options in a form: use a select or radios.\", use: \"radio-group\" },\n { when: \"As the only way to navigate: keep the visible navigation.\" },\n ],\n bestPractices: [\n \"Group the commands (`Command.Group heading`) and show their shortcuts with `Command.Shortcut`.\",\n \"Add `keywords` so a command is found by the words people type, not only by its label.\",\n \"Always say what an empty search means with `emptyLabel` on `Command.List` (said beside the listbox, never inside it).\",\n ],\n accessibility: {\n keyboard: [\n { keys: \"Arrow keys\", action: \"Move through the results.\" },\n { keys: \"Enter\", action: \"Runs the highlighted command.\" },\n { keys: \"Esc\", action: \"Closes the `CommandDialog` and returns the focus.\" },\n ],\n notes: [\n \"A combobox and a listbox: the input owns `aria-activedescendant`, the results are options.\",\n \"`label` names the palette, `Command.List label` the results.\",\n \"`CommandDialog` traps the focus in the platform's Dialog and puts it in the input when it opens.\",\n ],\n },\n related: [\"dialog\", \"dropdown-menu\", \"kbd\"],\n web: {\n imports: [\"Command\", \"CommandDialog\"],\n examples: [\n { name: \"inline\", title: \"Inline\", description: \"In a card: the input, groups, shortcuts, a disabled item.\" },\n { name: \"no-results\", title: \"No results\", description: \"`emptyLabel` on `Command.List`: said beside the listbox when nothing matches.\" },\n { name: \"dialog\", title: \"In a dialog\", description: \"`CommandDialog`: the palette of an app, opened from a button or a shortcut.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
|
|
228
243
|
},
|
|
229
244
|
{
|
|
230
245
|
"path": "meta.ts",
|
|
@@ -242,7 +257,7 @@
|
|
|
242
257
|
],
|
|
243
258
|
"demo": {
|
|
244
259
|
"path": "examples/command/inline.tsx",
|
|
245
|
-
"content": "import { Command } from \"@krizaka/ui/command\";\n\nexport default function CommandInline() {\n return (\n <Command.Root label=\"Commands\" className=\"w-[28rem] max-w-full rounded-xl border border-border-default shadow-lg\">\n <Command.Input placeholder=\"Type a command or search\" />\n <Command.List label=\"Suggestions\"
|
|
260
|
+
"content": "import { Command } from \"@krizaka/ui/command\";\n\nexport default function CommandInline() {\n return (\n <Command.Root label=\"Commands\" className=\"w-[28rem] max-w-full rounded-xl border border-border-default shadow-lg\">\n <Command.Input placeholder=\"Type a command or search\" />\n <Command.List label=\"Suggestions\" emptyLabel=\"Nothing matches.\">\n <Command.Group heading=\"Pages\">\n <Command.Item>\n Home\n <Command.Shortcut>G H</Command.Shortcut>\n </Command.Item>\n <Command.Item>\n Wallet\n <Command.Shortcut>G W</Command.Shortcut>\n </Command.Item>\n <Command.Item disabled>Studio</Command.Item>\n </Command.Group>\n <Command.Separator />\n <Command.Group heading=\"Actions\">\n <Command.Item keywords={[\"video\", \"new\"]}>Upload a video</Command.Item>\n <Command.Item>Switch the theme</Command.Item>\n </Command.Group>\n </Command.List>\n </Command.Root>\n );\n}\n"
|
|
246
261
|
},
|
|
247
262
|
"props": [
|
|
248
263
|
{
|
|
@@ -282,6 +297,13 @@
|
|
|
282
297
|
"component": "CommandList",
|
|
283
298
|
"description": "",
|
|
284
299
|
"props": [
|
|
300
|
+
{
|
|
301
|
+
"name": "emptyLabel",
|
|
302
|
+
"type": "ReactNode",
|
|
303
|
+
"default": null,
|
|
304
|
+
"description": "Shown when nothing matches — passed translated. Rendered after the listbox, never inside it: an empty listbox is\nvalid, a listbox holding text is not.",
|
|
305
|
+
"required": false
|
|
306
|
+
},
|
|
285
307
|
{
|
|
286
308
|
"name": "label",
|
|
287
309
|
"type": "string",
|
|
@@ -293,7 +315,7 @@
|
|
|
293
315
|
},
|
|
294
316
|
{
|
|
295
317
|
"component": "CommandEmpty",
|
|
296
|
-
"description": "Shown by cmdk only when no item matches.",
|
|
318
|
+
"description": "Shown by cmdk only when no item matches. Place it beside `Command.List` (or pass `emptyLabel` to the list), never\ninside it: inside, it is rendered as before but warns in development — the listbox then holds text, which axe\nrefuses (`aria-required-children`).",
|
|
297
319
|
"props": [
|
|
298
320
|
{
|
|
299
321
|
"name": "emptyLabel",
|
|
@@ -125,9 +125,9 @@
|
|
|
125
125
|
},
|
|
126
126
|
{
|
|
127
127
|
"name": "variant",
|
|
128
|
-
"type": "\"danger\" | \"primary\" | \"secondary\" | \"outline\" | \"ghost\"",
|
|
128
|
+
"type": "\"danger\" | \"primary\" | \"gradient\" | \"secondary\" | \"outline\" | \"ghost\"",
|
|
129
129
|
"default": "ghost",
|
|
130
|
-
"description": "The look: `primary` (the accent), `secondary` (a raised surface), `outline
|
|
130
|
+
"description": "The look: `primary` (the accent), `gradient` (accent → accent-2), `secondary` (a raised surface), `outline`,\n`ghost`, `danger`.",
|
|
131
131
|
"required": false
|
|
132
132
|
}
|
|
133
133
|
]
|
|
@@ -260,9 +260,9 @@
|
|
|
260
260
|
},
|
|
261
261
|
{
|
|
262
262
|
"name": "variant",
|
|
263
|
-
"type": "\"danger\" | \"primary\" | \"secondary\" | \"outline\" | \"ghost\"",
|
|
263
|
+
"type": "\"danger\" | \"primary\" | \"gradient\" | \"secondary\" | \"outline\" | \"ghost\"",
|
|
264
264
|
"default": "ghost",
|
|
265
|
-
"description": "The look: `primary` (the accent), `secondary` (a raised surface), `outline
|
|
265
|
+
"description": "The look: `primary` (the accent), `gradient` (accent → accent-2), `secondary` (a raised surface), `outline`,\n`ghost`, `danger`.",
|
|
266
266
|
"required": false
|
|
267
267
|
}
|
|
268
268
|
]
|
package/registry/countdown.json
CHANGED
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"props": [
|
|
48
48
|
{
|
|
49
49
|
"component": "Countdown",
|
|
50
|
-
"description": "Time left until a moment, as segments (2d 04h 13m 09s — days only when there are some) in tabular figures. Under\n`urgentBelowMs` it turns to the danger role and its last segment pulses (no motion under prefers-reduced-motion).\nWords come from the app: `units` are the short unit labels. `role=\"timer\"` without live announcements — the app\nannounces what matters. State: `data-urgent`, `data-ended`.\n\nThe
|
|
50
|
+
"description": "Time left until a moment, as segments (2d 04h 13m 09s — days only when there are some) in tabular figures. Under\n`urgentBelowMs` it turns to the danger text role and its last segment pulses (no motion under prefers-reduced-motion).\nWords come from the app: `units` are the short unit labels. `role=\"timer\"` without live announcements — the app\nannounces what matters. State: `data-urgent`, `data-ended`.\n\nThe urgent colour is `--kz-danger-text` (≥ 4.5:1 on every surface in both themes), so `size=\"sm\"` is safe too.",
|
|
51
51
|
"props": [
|
|
52
52
|
{
|
|
53
53
|
"name": "label",
|
|
@@ -77,11 +77,18 @@
|
|
|
77
77
|
"description": "Classes merged last, over the primitive's.",
|
|
78
78
|
"required": false
|
|
79
79
|
},
|
|
80
|
+
{
|
|
81
|
+
"name": "showLabel",
|
|
82
|
+
"type": "boolean",
|
|
83
|
+
"default": "false",
|
|
84
|
+
"description": "Also show `label` before the segments, in the secondary text role (default: only read by screen readers).",
|
|
85
|
+
"required": false
|
|
86
|
+
},
|
|
80
87
|
{
|
|
81
88
|
"name": "size",
|
|
82
89
|
"type": "\"sm\" | \"md\" | \"lg\"",
|
|
83
90
|
"default": "md",
|
|
84
|
-
"description": "sm · md · lg
|
|
91
|
+
"description": "sm · md · lg — the danger text role reads at 4.5:1 at every size, in both themes.",
|
|
85
92
|
"required": false
|
|
86
93
|
},
|
|
87
94
|
{
|
|
@@ -95,7 +102,7 @@
|
|
|
95
102
|
"name": "urgentBelowMs",
|
|
96
103
|
"type": "number",
|
|
97
104
|
"default": "60000",
|
|
98
|
-
"description": "Below this many milliseconds it turns urgent (`text-danger`, the last segment pulses). Default 60 s.",
|
|
105
|
+
"description": "Below this many milliseconds it turns urgent (`text-fg-danger`, the last segment pulses). Default 60 s.",
|
|
99
106
|
"required": false
|
|
100
107
|
}
|
|
101
108
|
]
|
|
@@ -129,11 +136,19 @@
|
|
|
129
136
|
{
|
|
130
137
|
"name": "urgent",
|
|
131
138
|
"title": "Urgent",
|
|
132
|
-
"description": "Under a minute: the danger role, the last segment pulses.",
|
|
139
|
+
"description": "Under a minute: the danger text role, the last segment pulses.",
|
|
133
140
|
"path": "examples/countdown/urgent.tsx",
|
|
134
141
|
"module": "@krizaka/ui/registry/examples/countdown/urgent",
|
|
135
142
|
"code": "\"use client\";\n\nimport { Countdown } from \"@krizaka/ui/countdown\";\nimport { useState } from \"react\";\n\n// Under `urgentBelowMs` (one minute by default): the danger role, the last segment pulses.\nexport default function CountdownUrgent() {\n const [target] = useState(() => Date.now() + 42_000);\n return <Countdown target={target} units={{ d: \"d\", h: \"h\", m: \"m\", s: \"s\" }} label=\"Ends in\" />;\n}\n"
|
|
136
143
|
},
|
|
144
|
+
{
|
|
145
|
+
"name": "with-label",
|
|
146
|
+
"title": "With its label",
|
|
147
|
+
"description": "`showLabel`: the label written before the segments.",
|
|
148
|
+
"path": "examples/countdown/with-label.tsx",
|
|
149
|
+
"module": "@krizaka/ui/registry/examples/countdown/with-label",
|
|
150
|
+
"code": "\"use client\";\n\nimport { Countdown } from \"@krizaka/ui/countdown\";\nimport { useState } from \"react\";\n\n// `showLabel` writes the label before the segments (it stays the accessible name).\nexport default function CountdownWithLabel() {\n const [target] = useState(() => Date.now() + 3_723_000);\n return <Countdown target={target} units={{ d: \"d\", h: \"h\", m: \"m\", s: \"s\" }} label=\"Ends in\" showLabel />;\n}\n"
|
|
151
|
+
},
|
|
137
152
|
{
|
|
138
153
|
"name": "ended",
|
|
139
154
|
"title": "Ended",
|
|
@@ -154,7 +169,7 @@
|
|
|
154
169
|
"props": [
|
|
155
170
|
{
|
|
156
171
|
"component": "Countdown",
|
|
157
|
-
"description": "Time left until a moment, as segments (2d 04h 13m — days only when there are some) in tabular figures, on the same\nclock as the web `Countdown` (one interval for every countdown on screen). Under `urgentBelowMs` it turns to the\ndanger role and its last segment breathes (still when the system reduces motion).",
|
|
172
|
+
"description": "Time left until a moment, as segments (2d 04h 13m — days only when there are some) in tabular figures, on the same\nclock as the web `Countdown` (one interval for every countdown on screen). Under `urgentBelowMs` it turns to the\ndanger text role (≥ 4.5:1 in both themes) and its last segment breathes (still when the system reduces motion).\n`showLabel` writes `label` before the segments; it is the accessible name either way.",
|
|
158
173
|
"props": [
|
|
159
174
|
{
|
|
160
175
|
"name": "label",
|
|
@@ -177,6 +192,13 @@
|
|
|
177
192
|
"description": "The short unit labels, passed translated.",
|
|
178
193
|
"required": true
|
|
179
194
|
},
|
|
195
|
+
{
|
|
196
|
+
"name": "showLabel",
|
|
197
|
+
"type": "boolean",
|
|
198
|
+
"default": "false",
|
|
199
|
+
"description": "Also show `label` before the segments, in the secondary text role (default: only read by screen readers).",
|
|
200
|
+
"required": false
|
|
201
|
+
},
|
|
180
202
|
{
|
|
181
203
|
"name": "size",
|
|
182
204
|
"type": "\"sm\" | \"md\" | \"lg\"",
|
|
@@ -202,7 +224,7 @@
|
|
|
202
224
|
"name": "urgentBelowMs",
|
|
203
225
|
"type": "number",
|
|
204
226
|
"default": "60000",
|
|
205
|
-
"description": "Below this many milliseconds it turns urgent (the danger role, the last segment breathes). Default 60 s.",
|
|
227
|
+
"description": "Below this many milliseconds it turns urgent (the danger text role, the last segment breathes). Default 60 s.",
|
|
206
228
|
"required": false
|
|
207
229
|
}
|
|
208
230
|
]
|
|
@@ -253,7 +275,7 @@
|
|
|
253
275
|
{
|
|
254
276
|
"name": "urgent",
|
|
255
277
|
"title": "Urgent",
|
|
256
|
-
"description": "Under a minute: the danger role.",
|
|
278
|
+
"description": "Under a minute: the danger text role.",
|
|
257
279
|
"path": "examples/countdown/native/urgent.tsx",
|
|
258
280
|
"module": "@krizaka/ui/registry/examples/countdown/native/urgent",
|
|
259
281
|
"code": "import { Countdown } from \"@krizaka/ui/native\";\nimport { useState } from \"react\";\n\nexport default function NativeCountdownUrgent() {\n const [target] = useState(() => Date.now() + 42_000);\n return <Countdown target={target} units={{ d: \"d\", h: \"h\", m: \"m\", s: \"s\" }} label=\"Ends in\" size=\"lg\" />;\n}\n",
|
|
@@ -261,6 +283,18 @@
|
|
|
261
283
|
"dark": "examples/countdown/native/urgent.dark.png",
|
|
262
284
|
"light": "examples/countdown/native/urgent.light.png"
|
|
263
285
|
}
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
"name": "with-label",
|
|
289
|
+
"title": "With its label",
|
|
290
|
+
"description": "`showLabel`: the label written before the segments.",
|
|
291
|
+
"path": "examples/countdown/native/with-label.tsx",
|
|
292
|
+
"module": "@krizaka/ui/registry/examples/countdown/native/with-label",
|
|
293
|
+
"code": "import { Countdown } from \"@krizaka/ui/native\";\nimport { useState } from \"react\";\n\nexport default function NativeCountdownWithLabel() {\n const [target] = useState(() => Date.now() + 3_723_000);\n return <Countdown target={target} units={{ d: \"d\", h: \"h\", m: \"m\", s: \"s\" }} label=\"Ends in\" showLabel />;\n}\n",
|
|
294
|
+
"screenshots": {
|
|
295
|
+
"dark": "examples/countdown/native/with-label.dark.png",
|
|
296
|
+
"light": "examples/countdown/native/with-label.light.png"
|
|
297
|
+
}
|
|
264
298
|
}
|
|
265
299
|
]
|
|
266
300
|
},
|
|
@@ -271,7 +305,7 @@
|
|
|
271
305
|
},
|
|
272
306
|
{
|
|
273
307
|
"path": "countdown/countdown.tsx",
|
|
274
|
-
"content": "// Client: the clock is a hook.\nimport { tv } from \"tailwind-variants\";\n\nimport { countdownParts, type CountdownUnits, useCountdown } from \"./core\";\n\nconst countdown = tv({\n slots: {\n root: \"inline-flex items-baseline gap-1.5 font-display font-black tabular-nums tracking-tight text-fg\",\n segment: \"inline-flex items-baseline\",\n unit: \"ml-0.5 text-[0.5em] font-bold uppercase text-fg-secondary\",\n },\n variants: {\n size: { sm: { root: \"text-sm\" }, md: { root: \"text-xl\" }, lg: { root: \"text-3xl sm:text-4xl\" } },\n urgent: { true: { root: \"text-danger\" } },\n },\n defaultVariants: { size: \"md\" },\n});\n\nexport type CountdownProps = {\n /** The moment it counts down to: a Date, an ISO string or epoch milliseconds. */\n target: Date | string | number;\n /** Server clock − this clock, in milliseconds. */\n skewMs?: number;\n /** The short unit labels, passed translated. */\n units: CountdownUnits;\n /** Below this many milliseconds it turns urgent (`text-danger`, the last segment pulses). Default 60 s. */\n urgentBelowMs?: number;\n /** sm · md · lg
|
|
308
|
+
"content": "// Client: the clock is a hook.\nimport { tv } from \"tailwind-variants\";\n\nimport { countdownParts, type CountdownUnits, useCountdown } from \"./core\";\n\nconst countdown = tv({\n slots: {\n root: \"inline-flex items-baseline gap-1.5 font-display font-black tabular-nums tracking-tight text-fg\",\n segment: \"inline-flex items-baseline\",\n unit: \"ml-0.5 text-[0.5em] font-bold uppercase text-fg-secondary\",\n label: \"mr-0.5 self-center font-sans text-[0.6em] font-semibold tracking-normal text-fg-secondary\",\n },\n variants: {\n size: { sm: { root: \"text-sm\" }, md: { root: \"text-xl\" }, lg: { root: \"text-3xl sm:text-4xl\" } },\n urgent: { true: { root: \"text-fg-danger\" } },\n },\n defaultVariants: { size: \"md\" },\n});\n\nexport type CountdownProps = {\n /** The moment it counts down to: a Date, an ISO string or epoch milliseconds. */\n target: Date | string | number;\n /** Server clock − this clock, in milliseconds. */\n skewMs?: number;\n /** The short unit labels, passed translated. */\n units: CountdownUnits;\n /** Below this many milliseconds it turns urgent (`text-fg-danger`, the last segment pulses). Default 60 s. */\n urgentBelowMs?: number;\n /** sm · md · lg — the danger text role reads at 4.5:1 at every size, in both themes. */\n size?: \"sm\" | \"md\" | \"lg\";\n /** Accessible name, e.g. \"Ends in\". */\n label: string;\n /** Also show `label` before the segments, in the secondary text role (default: only read by screen readers). */\n showLabel?: boolean;\n /** Classes merged last, over the primitive's. */\n className?: string;\n};\n\n/**\n * Time left until a moment, as segments (2d 04h 13m 09s — days only when there are some) in tabular figures. Under\n * `urgentBelowMs` it turns to the danger text role and its last segment pulses (no motion under prefers-reduced-motion).\n * Words come from the app: `units` are the short unit labels. `role=\"timer\"` without live announcements — the app\n * announces what matters. State: `data-urgent`, `data-ended`.\n *\n * The urgent colour is `--kz-danger-text` (≥ 4.5:1 on every surface in both themes), so `size=\"sm\"` is safe too.\n */\nexport function Countdown({ target, skewMs = 0, units, urgentBelowMs = 60_000, size = \"md\", label, showLabel = false, className }: CountdownProps) {\n const ms = useCountdown(target, skewMs);\n const urgent = ms > 0 && ms < urgentBelowMs;\n const parts = countdownParts(ms, units);\n const s = countdown({ size, urgent });\n return (\n <span\n role=\"timer\"\n aria-label={label}\n data-urgent={urgent ? \"\" : undefined}\n data-ended={ms === 0 ? \"\" : undefined}\n suppressHydrationWarning\n className={s.root({ className })}\n >\n {showLabel ? (\n <span aria-hidden className={s.label()}>\n {label}\n </span>\n ) : null}\n {parts.map(([value, unit], i) => (\n <span key={unit} className={s.segment({ className: urgent && i === parts.length - 1 ? \"motion-safe:animate-pulse\" : undefined })}>\n {String(value).padStart(2, \"0\")}\n <span className={s.unit()}>{unit}</span>\n </span>\n ))}\n </span>\n );\n}\n"
|
|
275
309
|
},
|
|
276
310
|
{
|
|
277
311
|
"path": "countdown/index.ts",
|
|
@@ -279,7 +313,7 @@
|
|
|
279
313
|
},
|
|
280
314
|
{
|
|
281
315
|
"path": "countdown/meta.ts",
|
|
282
|
-
"content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Countdown\",\n summary: \"The time left until a moment, in tabular figures, turning urgent under a threshold.\",\n status: \"stable\",\n category: \"data-display\",\n platforms: \"both\",\n whenToUse: [\n \"For a deadline the user acts against: an auction, an offer, a live event about to start.\",\n \"With the server's clock (`skewMs`) when the end is decided by the server.\",\n ],\n whenNotToUse: [\n { when: \"For a date far away: write the date (“Ends on 12 May”).\" },\n { when: \"For the progress of a task.\", use: \"progress\" },\n { when: \"For an elapsed time that keeps growing: format a duration in the text.\" },\n ],\n bestPractices: [\n \"Pass `units` and `label` translated: they are the app's words.\",\n \"Use `size=\\\"md\\\"` or larger when it can turn urgent: the danger colour is large-text safe from `md`.\",\n \"Say what happens at zero next to it: the countdown only shows zeros.\",\n ],\n accessibility: {\n keyboard: [],\n notes: [\n \"`role=\\\"timer\\\"` named by `label`, without live announcements: a screen reader is not interrupted every second.\",\n \"Announce the end yourself (a toast, a status) if it matters.\",\n \"The pulse of the last segment stops under `prefers-reduced-motion`.\",\n ],\n },\n related: [\"progress\", \"badge\", \"stat\"],\n web: {\n imports: [\"Countdown\"],\n examples: [\n { name: \"hours\", title: \"Hours\", description: \"Hours, minutes and seconds.\" },\n { name: \"days\", title: \"Days\", description: \"Days appear when there are some.\" },\n { name: \"sizes\", title: \"Sizes\", description: \"`sm`, `md` (default), `lg`.\" },\n { name: \"urgent\", title: \"Urgent\", description: \"Under a minute: the danger role, the last segment pulses.\" },\n { name: \"ended\", title: \"Ended\", description: \"Past the target: zeros and `data-ended`.\" },\n ],\n },\n native: {\n imports: [\"Countdown\"],\n differences: [\"Same props and same clock as the web; style with `style`.\", \"`useCountdown` and `splitDuration` are exported for a custom layout.\"],\n examples: [\n { name: \"hours\", title: \"Hours\", description: \"Hours, minutes, seconds.\" },\n { name: \"days\", title: \"Days\", description: \"Days, hours, minutes.\" },\n { name: \"sizes\", title: \"Sizes\", description: \"`sm`, `md`, `lg`.\" },\n { name: \"urgent\", title: \"Urgent\", description: \"Under a minute: the danger role.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
|
|
316
|
+
"content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Countdown\",\n summary: \"The time left until a moment, in tabular figures, turning urgent under a threshold.\",\n status: \"stable\",\n category: \"data-display\",\n platforms: \"both\",\n whenToUse: [\n \"For a deadline the user acts against: an auction, an offer, a live event about to start.\",\n \"With the server's clock (`skewMs`) when the end is decided by the server.\",\n ],\n whenNotToUse: [\n { when: \"For a date far away: write the date (“Ends on 12 May”).\" },\n { when: \"For the progress of a task.\", use: \"progress\" },\n { when: \"For an elapsed time that keeps growing: format a duration in the text.\" },\n ],\n bestPractices: [\n \"Pass `units` and `label` translated: they are the app's words.\",\n \"Use `size=\\\"md\\\"` or larger when it can turn urgent: the danger colour is large-text safe from `md`.\",\n \"Say what happens at zero next to it: the countdown only shows zeros.\",\n ],\n accessibility: {\n keyboard: [],\n notes: [\n \"`role=\\\"timer\\\"` named by `label`, without live announcements: a screen reader is not interrupted every second.\",\n \"Announce the end yourself (a toast, a status) if it matters.\",\n \"The pulse of the last segment stops under `prefers-reduced-motion`.\",\n ],\n },\n related: [\"progress\", \"badge\", \"stat\"],\n web: {\n imports: [\"Countdown\"],\n examples: [\n { name: \"hours\", title: \"Hours\", description: \"Hours, minutes and seconds.\" },\n { name: \"days\", title: \"Days\", description: \"Days appear when there are some.\" },\n { name: \"sizes\", title: \"Sizes\", description: \"`sm`, `md` (default), `lg`.\" },\n { name: \"urgent\", title: \"Urgent\", description: \"Under a minute: the danger text role, the last segment pulses.\" },\n { name: \"with-label\", title: \"With its label\", description: \"`showLabel`: the label written before the segments.\" },\n { name: \"ended\", title: \"Ended\", description: \"Past the target: zeros and `data-ended`.\" },\n ],\n },\n native: {\n imports: [\"Countdown\"],\n differences: [\"Same props and same clock as the web; style with `style`.\", \"`useCountdown` and `splitDuration` are exported for a custom layout.\"],\n examples: [\n { name: \"hours\", title: \"Hours\", description: \"Hours, minutes, seconds.\" },\n { name: \"days\", title: \"Days\", description: \"Days, hours, minutes.\" },\n { name: \"sizes\", title: \"Sizes\", description: \"`sm`, `md`, `lg`.\" },\n { name: \"urgent\", title: \"Urgent\", description: \"Under a minute: the danger text role.\" },\n { name: \"with-label\", title: \"With its label\", description: \"`showLabel`: the label written before the segments.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
|
|
283
317
|
},
|
|
284
318
|
{
|
|
285
319
|
"path": "meta.ts",
|
|
@@ -297,7 +331,7 @@
|
|
|
297
331
|
"props": [
|
|
298
332
|
{
|
|
299
333
|
"component": "Countdown",
|
|
300
|
-
"description": "Time left until a moment, as segments (2d 04h 13m 09s — days only when there are some) in tabular figures. Under\n`urgentBelowMs` it turns to the danger role and its last segment pulses (no motion under prefers-reduced-motion).\nWords come from the app: `units` are the short unit labels. `role=\"timer\"` without live announcements — the app\nannounces what matters. State: `data-urgent`, `data-ended`.\n\nThe
|
|
334
|
+
"description": "Time left until a moment, as segments (2d 04h 13m 09s — days only when there are some) in tabular figures. Under\n`urgentBelowMs` it turns to the danger text role and its last segment pulses (no motion under prefers-reduced-motion).\nWords come from the app: `units` are the short unit labels. `role=\"timer\"` without live announcements — the app\nannounces what matters. State: `data-urgent`, `data-ended`.\n\nThe urgent colour is `--kz-danger-text` (≥ 4.5:1 on every surface in both themes), so `size=\"sm\"` is safe too.",
|
|
301
335
|
"props": [
|
|
302
336
|
{
|
|
303
337
|
"name": "label",
|
|
@@ -327,11 +361,18 @@
|
|
|
327
361
|
"description": "Classes merged last, over the primitive's.",
|
|
328
362
|
"required": false
|
|
329
363
|
},
|
|
364
|
+
{
|
|
365
|
+
"name": "showLabel",
|
|
366
|
+
"type": "boolean",
|
|
367
|
+
"default": "false",
|
|
368
|
+
"description": "Also show `label` before the segments, in the secondary text role (default: only read by screen readers).",
|
|
369
|
+
"required": false
|
|
370
|
+
},
|
|
330
371
|
{
|
|
331
372
|
"name": "size",
|
|
332
373
|
"type": "\"sm\" | \"md\" | \"lg\"",
|
|
333
374
|
"default": "md",
|
|
334
|
-
"description": "sm · md · lg
|
|
375
|
+
"description": "sm · md · lg — the danger text role reads at 4.5:1 at every size, in both themes.",
|
|
335
376
|
"required": false
|
|
336
377
|
},
|
|
337
378
|
{
|
|
@@ -345,7 +386,7 @@
|
|
|
345
386
|
"name": "urgentBelowMs",
|
|
346
387
|
"type": "number",
|
|
347
388
|
"default": "60000",
|
|
348
|
-
"description": "Below this many milliseconds it turns urgent (`text-danger`, the last segment pulses). Default 60 s.",
|
|
389
|
+
"description": "Below this many milliseconds it turns urgent (`text-fg-danger`, the last segment pulses). Default 60 s.",
|
|
349
390
|
"required": false
|
|
350
391
|
}
|
|
351
392
|
]
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
"files": [
|
|
111
111
|
{
|
|
112
112
|
"path": "dropdown-menu/dropdown-menu.tsx",
|
|
113
|
-
"content": "// Client (a portal). Radix does the roving focus, the arrows, Home/End, typeahead, Escape and the focus return.\nimport { DropdownMenu as MenuPrimitive } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { cn } from \"../cn\";\nimport { floating } from \"../floating\";\n\nexport const menu = tv({\n slots: {\n content: cn(floating, \"min-w-48 max-h-(--radix-dropdown-menu-content-available-height) overflow-y-auto p-1 origin-(--radix-dropdown-menu-content-transform-origin)\"),\n item:\n \"relative flex cursor-pointer select-none items-center gap-2 rounded-md px-2.5 py-2 text-sm text-fg outline-hidden transition-colors \" +\n \"data-highlighted:bg-surface-3 data-disabled:pointer-events-none data-disabled:opacity-40 [&>svg]:h-4 [&>svg]:w-4 [&>svg]:shrink-0 \" +\n \"[&>svg]:text-fg-secondary\",\n indicator: \"ml-auto flex h-4 w-4 items-center justify-center text-fg-accent\",\n label: \"px-2.5 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-wider text-fg-secondary\",\n separator: \"-mx-1 my-1 h-px bg-border-subtle\",\n },\n variants: {\n // The
|
|
113
|
+
"content": "// Client (a portal). Radix does the roving focus, the arrows, Home/End, typeahead, Escape and the focus return.\nimport { DropdownMenu as MenuPrimitive } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { cn } from \"../cn\";\nimport { floating } from \"../floating\";\n\nexport const menu = tv({\n slots: {\n content: cn(floating, \"min-w-48 max-h-(--radix-dropdown-menu-content-available-height) overflow-y-auto p-1 origin-(--radix-dropdown-menu-content-transform-origin)\"),\n item:\n \"relative flex cursor-pointer select-none items-center gap-2 rounded-md px-2.5 py-2 text-sm text-fg outline-hidden transition-colors \" +\n \"data-highlighted:bg-surface-3 data-disabled:pointer-events-none data-disabled:opacity-40 [&>svg]:h-4 [&>svg]:w-4 [&>svg]:shrink-0 \" +\n \"[&>svg]:text-fg-secondary\",\n indicator: \"ml-auto flex h-4 w-4 items-center justify-center text-fg-accent\",\n label: \"px-2.5 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-wider text-fg-secondary\",\n separator: \"-mx-1 my-1 h-px bg-border-subtle\",\n },\n variants: {\n // The label reads in the danger text role (≥ 4.5:1 on the menu surface, both themes); the icon and the highlight\n // carry the danger fill.\n tone: { default: {}, danger: { item: \"text-fg-danger data-highlighted:bg-danger/15 [&>svg]:text-danger\" } },\n },\n defaultVariants: { tone: \"default\" },\n});\nconst s = menu();\n\nexport type DropdownMenuContentProps = React.ComponentProps<typeof MenuPrimitive.Content> & {\n /** The element the portal renders into (default: document.body). */\n container?: HTMLElement | null;\n};\n\nexport function DropdownMenuContent({ sideOffset = 6, align = \"end\", collisionPadding = 8, container, className, ...props }: DropdownMenuContentProps) {\n return (\n <MenuPrimitive.Portal container={container}>\n <MenuPrimitive.Content sideOffset={sideOffset} align={align} collisionPadding={collisionPadding} className={s.content({ className })} {...props} />\n </MenuPrimitive.Portal>\n );\n}\n\nexport type DropdownMenuItemProps = React.ComponentProps<typeof MenuPrimitive.Item> & {\n /** `danger` for a destructive action: the icon and the highlight carry the danger, the label stays legible. */\n tone?: \"default\" | \"danger\";\n};\n\n/** An action. `tone=\"danger\"` for a destructive one; `onSelect` runs it (the menu closes). */\nexport function DropdownMenuItem({ tone, className, ...props }: DropdownMenuItemProps) {\n return <MenuPrimitive.Item data-tone={tone ?? \"default\"} className={menu({ tone }).item({ className })} {...props} />;\n}\n\n/** A toggled option: `checked` + `onCheckedChange`. */\nexport function DropdownMenuCheckboxItem({ className, children, ...props }: React.ComponentProps<typeof MenuPrimitive.CheckboxItem>) {\n return (\n <MenuPrimitive.CheckboxItem className={s.item({ className })} {...props}>\n {children}\n <MenuPrimitive.ItemIndicator className={s.indicator()}>\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n </MenuPrimitive.ItemIndicator>\n </MenuPrimitive.CheckboxItem>\n );\n}\n\nexport function DropdownMenuLabel({ className, ...props }: React.ComponentProps<typeof MenuPrimitive.Label>) {\n return <MenuPrimitive.Label className={s.label({ className })} {...props} />;\n}\n\nexport function DropdownMenuSeparator({ className, ...props }: React.ComponentProps<typeof MenuPrimitive.Separator>) {\n return <MenuPrimitive.Separator className={s.separator({ className })} {...props} />;\n}\n\n/** `DropdownMenu.Root/Trigger/Content/Item/CheckboxItem/Label/Separator/Group`. `open`/`defaultOpen` + `onOpenChange`. */\nexport const DropdownMenu = {\n Root: MenuPrimitive.Root,\n Trigger: MenuPrimitive.Trigger,\n Content: DropdownMenuContent,\n Item: DropdownMenuItem,\n CheckboxItem: DropdownMenuCheckboxItem,\n Label: DropdownMenuLabel,\n Separator: DropdownMenuSeparator,\n Group: MenuPrimitive.Group,\n};\n"
|
|
114
114
|
},
|
|
115
115
|
{
|
|
116
116
|
"path": "dropdown-menu/index.ts",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// registry/examples/button/gradient.tsx
|
|
4
|
+
import { Button } from "@krizaka/ui/button";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
function ButtonGradient() {
|
|
7
|
+
return /* @__PURE__ */ jsx(Button, { variant: "gradient", children: "Start free" });
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
ButtonGradient as default
|
|
11
|
+
};
|
|
Binary file
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// registry/examples/button/native/gradient.tsx
|
|
4
|
+
import { Button } from "@krizaka/ui/native";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
function NativeButtonGradient() {
|
|
7
|
+
return /* @__PURE__ */ jsx(Button, { variant: "gradient", label: "Start free", onPress: () => {
|
|
8
|
+
} });
|
|
9
|
+
}
|
|
10
|
+
export {
|
|
11
|
+
NativeButtonGradient as default
|
|
12
|
+
};
|
|
Binary file
|
|
@@ -22,8 +22,7 @@ function CommandDialogExample({ defaultOpen = false }) {
|
|
|
22
22
|
footer: /* @__PURE__ */ jsx("p", { className: "border-t border-border-subtle bg-surface-2 px-4 py-2.5 text-xs text-fg-secondary", children: "\u2191\u2193 to move, \u21B5 to open" }),
|
|
23
23
|
children: [
|
|
24
24
|
/* @__PURE__ */ jsx(Command.Input, { placeholder: "Search creators, videos, tags" }),
|
|
25
|
-
/* @__PURE__ */ jsxs(Command.List, { label: "Results", children: [
|
|
26
|
-
/* @__PURE__ */ jsx(Command.Empty, { emptyLabel: "Nothing matches." }),
|
|
25
|
+
/* @__PURE__ */ jsxs(Command.List, { label: "Results", emptyLabel: "Nothing matches.", children: [
|
|
27
26
|
/* @__PURE__ */ jsxs(Command.Group, { heading: "Pages", children: [
|
|
28
27
|
/* @__PURE__ */ jsx(Command.Item, { onSelect: () => setOpen(false), children: "Home" }),
|
|
29
28
|
/* @__PURE__ */ jsx(Command.Item, { onSelect: () => setOpen(false), children: "Wallet" })
|
|
@@ -20,8 +20,7 @@ export default function CommandDialogExample({ defaultOpen = false }: { defaultO
|
|
|
20
20
|
footer={<p className="border-t border-border-subtle bg-surface-2 px-4 py-2.5 text-xs text-fg-secondary">↑↓ to move, ↵ to open</p>}
|
|
21
21
|
>
|
|
22
22
|
<Command.Input placeholder="Search creators, videos, tags" />
|
|
23
|
-
<Command.List label="Results">
|
|
24
|
-
<Command.Empty emptyLabel="Nothing matches." />
|
|
23
|
+
<Command.List label="Results" emptyLabel="Nothing matches.">
|
|
25
24
|
<Command.Group heading="Pages">
|
|
26
25
|
<Command.Item onSelect={() => setOpen(false)}>Home</Command.Item>
|
|
27
26
|
<Command.Item onSelect={() => setOpen(false)}>Wallet</Command.Item>
|
|
@@ -6,8 +6,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
function CommandInline() {
|
|
7
7
|
return /* @__PURE__ */ jsxs(Command.Root, { label: "Commands", className: "w-[28rem] max-w-full rounded-xl border border-border-default shadow-lg", children: [
|
|
8
8
|
/* @__PURE__ */ jsx(Command.Input, { placeholder: "Type a command or search" }),
|
|
9
|
-
/* @__PURE__ */ jsxs(Command.List, { label: "Suggestions", children: [
|
|
10
|
-
/* @__PURE__ */ jsx(Command.Empty, { emptyLabel: "Nothing matches." }),
|
|
9
|
+
/* @__PURE__ */ jsxs(Command.List, { label: "Suggestions", emptyLabel: "Nothing matches.", children: [
|
|
11
10
|
/* @__PURE__ */ jsxs(Command.Group, { heading: "Pages", children: [
|
|
12
11
|
/* @__PURE__ */ jsxs(Command.Item, { children: [
|
|
13
12
|
"Home",
|
|
@@ -4,8 +4,7 @@ export default function CommandInline() {
|
|
|
4
4
|
return (
|
|
5
5
|
<Command.Root label="Commands" className="w-[28rem] max-w-full rounded-xl border border-border-default shadow-lg">
|
|
6
6
|
<Command.Input placeholder="Type a command or search" />
|
|
7
|
-
<Command.List label="Suggestions">
|
|
8
|
-
<Command.Empty emptyLabel="Nothing matches." />
|
|
7
|
+
<Command.List label="Suggestions" emptyLabel="Nothing matches.">
|
|
9
8
|
<Command.Group heading="Pages">
|
|
10
9
|
<Command.Item>
|
|
11
10
|
Home
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// registry/examples/command/no-results.tsx
|
|
4
|
+
import { Command } from "@krizaka/ui/command";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
function CommandNoResults() {
|
|
8
|
+
const [query, setQuery] = useState("zzz");
|
|
9
|
+
return /* @__PURE__ */ jsxs(Command.Root, { label: "Commands", className: "w-[28rem] max-w-full rounded-xl border border-border-default shadow-lg", children: [
|
|
10
|
+
/* @__PURE__ */ jsx(Command.Input, { placeholder: "Type a command or search", value: query, onValueChange: setQuery }),
|
|
11
|
+
/* @__PURE__ */ jsx(Command.List, { label: "Suggestions", emptyLabel: `Nothing matches \u201C${query}\u201D.`, children: /* @__PURE__ */ jsxs(Command.Group, { heading: "Pages", children: [
|
|
12
|
+
/* @__PURE__ */ jsx(Command.Item, { children: "Home" }),
|
|
13
|
+
/* @__PURE__ */ jsx(Command.Item, { children: "Wallet" })
|
|
14
|
+
] }) })
|
|
15
|
+
] });
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
CommandNoResults as default
|
|
19
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Command } from "@krizaka/ui/command";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
|
|
6
|
+
// Nothing matches: `emptyLabel` is said beside the results, the listbox stays empty (valid for screen readers).
|
|
7
|
+
export default function CommandNoResults() {
|
|
8
|
+
const [query, setQuery] = useState("zzz");
|
|
9
|
+
return (
|
|
10
|
+
<Command.Root label="Commands" className="w-[28rem] max-w-full rounded-xl border border-border-default shadow-lg">
|
|
11
|
+
<Command.Input placeholder="Type a command or search" value={query} onValueChange={setQuery} />
|
|
12
|
+
<Command.List label="Suggestions" emptyLabel={`Nothing matches “${query}”.`}>
|
|
13
|
+
<Command.Group heading="Pages">
|
|
14
|
+
<Command.Item>Home</Command.Item>
|
|
15
|
+
<Command.Item>Wallet</Command.Item>
|
|
16
|
+
</Command.Group>
|
|
17
|
+
</Command.List>
|
|
18
|
+
</Command.Root>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|