@krizaka/ui 2.0.0-beta.2 → 2.0.0-beta.4
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 +98 -6
- package/dist/alert.d.ts +2 -0
- package/dist/alert.js.map +1 -1
- package/dist/avatar.d.ts +5 -0
- package/dist/avatar.js.map +1 -1
- package/dist/badge.d.ts +12 -0
- package/dist/badge.js.map +1 -1
- package/dist/{button-G5qY4qSY.d.ts → button-8La7l0V4.d.ts} +15 -0
- package/dist/button.d.ts +19 -0
- package/dist/button.js.map +1 -1
- package/dist/card.d.ts +24 -1
- package/dist/card.js.map +1 -1
- package/dist/chip.d.ts +23 -0
- package/dist/chip.js.map +1 -1
- package/dist/{chunk-5FRYKFTO.js → chunk-3X5MVH54.js} +2 -2
- package/dist/chunk-3X5MVH54.js.map +1 -0
- package/dist/{chunk-QRG5RYEI.js → chunk-ZUD4U22H.js} +1 -1
- package/dist/chunk-ZUD4U22H.js.map +1 -0
- package/dist/command.d.ts +4 -1
- package/dist/command.js +2 -2
- 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 +3 -0
- package/dist/countdown.js.map +1 -1
- package/dist/{dialog-DGBY2grk.d.ts → dialog-GQ4Nvuyf.d.ts} +10 -0
- package/dist/dialog.d.ts +9 -1
- package/dist/dialog.js +2 -2
- package/dist/dialog.js.map +1 -1
- package/dist/dropdown-menu.d.ts +1 -0
- package/dist/dropdown-menu.js.map +1 -1
- package/dist/empty-state.d.ts +2 -0
- package/dist/empty-state.js.map +1 -1
- package/dist/kbd.d.ts +7 -1
- package/dist/kbd.js.map +1 -1
- package/dist/native.d.ts +471 -3
- package/dist/native.js +1005 -36
- package/dist/native.js.map +1 -1
- package/dist/page-header.d.ts +2 -0
- package/dist/page-header.js.map +1 -1
- package/dist/progress.d.ts +5 -0
- package/dist/progress.js.map +1 -1
- package/dist/skeleton.d.ts +7 -1
- package/dist/skeleton.js.map +1 -1
- package/dist/slider.d.ts +2 -0
- package/dist/slider.js.map +1 -1
- package/dist/spinner.d.ts +5 -0
- package/dist/spinner.js.map +1 -1
- package/dist/stat.d.ts +2 -0
- package/dist/stat.js.map +1 -1
- package/dist/switch.d.ts +7 -3
- package/dist/switch.js.map +1 -1
- package/dist/tabs.d.ts +4 -1
- package/dist/tabs.js.map +1 -1
- package/dist/theme.d.ts +10 -2
- package/dist/theme.js +4 -2
- package/dist/theme.js.map +1 -1
- package/dist/tooltip.d.ts +3 -0
- package/dist/tooltip.js.map +1 -1
- package/package.json +31 -8
- package/registry/alert.json +59 -0
- package/registry/avatar.json +114 -0
- package/registry/badge.json +59 -0
- package/registry/button.json +115 -0
- package/registry/card.json +154 -0
- package/registry/checkbox.json +46 -0
- package/registry/chip.json +164 -0
- package/registry/cn.json +21 -0
- package/registry/command.json +162 -0
- package/registry/confirm-button.json +104 -0
- package/registry/countdown.json +84 -0
- package/registry/demos/alert.d.ts +5 -0
- package/registry/demos/alert.js +12 -0
- package/registry/demos/alert.tsx +15 -0
- package/registry/demos/avatar.d.ts +5 -0
- package/registry/demos/avatar.js +14 -0
- package/registry/demos/avatar.tsx +13 -0
- package/registry/demos/badge.d.ts +5 -0
- package/registry/demos/badge.js +11 -0
- package/registry/demos/badge.tsx +5 -0
- package/registry/demos/button.d.ts +5 -0
- package/registry/demos/button.js +11 -0
- package/registry/demos/button.tsx +5 -0
- package/registry/demos/card.d.ts +5 -0
- package/registry/demos/card.js +30 -0
- package/registry/demos/card.tsx +37 -0
- package/registry/demos/checkbox.d.ts +5 -0
- package/registry/demos/checkbox.js +11 -0
- package/registry/demos/checkbox.tsx +5 -0
- package/registry/demos/chip.d.ts +5 -0
- package/registry/demos/chip.js +15 -0
- package/registry/demos/chip.tsx +11 -0
- package/registry/demos/cn.d.ts +7 -0
- package/registry/demos/cn.js +17 -0
- package/registry/demos/cn.tsx +9 -0
- package/registry/demos/command.d.ts +5 -0
- package/registry/demos/command.js +32 -0
- package/registry/demos/command.tsx +28 -0
- package/registry/demos/confirm-button.d.ts +5 -0
- package/registry/demos/confirm-button.js +12 -0
- package/registry/demos/confirm-button.tsx +13 -0
- package/registry/demos/countdown.d.ts +5 -0
- package/registry/demos/countdown.js +13 -0
- package/registry/demos/countdown.tsx +10 -0
- package/registry/demos/dialog.d.ts +7 -0
- package/registry/demos/dialog.js +29 -0
- package/registry/demos/dialog.tsx +31 -0
- package/registry/demos/dropdown-menu.d.ts +7 -0
- package/registry/demos/dropdown-menu.js +36 -0
- package/registry/demos/dropdown-menu.tsx +27 -0
- package/registry/demos/empty-state.d.ts +5 -0
- package/registry/demos/empty-state.js +15 -0
- package/registry/demos/empty-state.tsx +12 -0
- package/registry/demos/field.d.ts +5 -0
- package/registry/demos/field.js +15 -0
- package/registry/demos/field.tsx +11 -0
- package/registry/demos/kbd.d.ts +5 -0
- package/registry/demos/kbd.js +11 -0
- package/registry/demos/kbd.tsx +5 -0
- package/registry/demos/page-header.d.ts +5 -0
- package/registry/demos/page-header.js +11 -0
- package/registry/demos/page-header.tsx +5 -0
- package/registry/demos/popover.d.ts +7 -0
- package/registry/demos/popover.js +25 -0
- package/registry/demos/popover.tsx +31 -0
- package/registry/demos/progress.d.ts +5 -0
- package/registry/demos/progress.js +11 -0
- package/registry/demos/progress.tsx +5 -0
- package/registry/demos/radio-group.d.ts +5 -0
- package/registry/demos/radio-group.js +16 -0
- package/registry/demos/radio-group.tsx +14 -0
- package/registry/demos/separator.d.ts +5 -0
- package/registry/demos/separator.js +15 -0
- package/registry/demos/separator.tsx +11 -0
- package/registry/demos/skeleton.d.ts +5 -0
- package/registry/demos/skeleton.js +20 -0
- package/registry/demos/skeleton.tsx +16 -0
- package/registry/demos/slider.d.ts +5 -0
- package/registry/demos/slider.js +11 -0
- package/registry/demos/slider.tsx +5 -0
- package/registry/demos/slot.d.ts +5 -0
- package/registry/demos/slot.js +14 -0
- package/registry/demos/slot.tsx +13 -0
- package/registry/demos/spinner.d.ts +5 -0
- package/registry/demos/spinner.js +11 -0
- package/registry/demos/spinner.tsx +5 -0
- package/registry/demos/stat.d.ts +5 -0
- package/registry/demos/stat.js +11 -0
- package/registry/demos/stat.tsx +5 -0
- package/registry/demos/switch.d.ts +5 -0
- package/registry/demos/switch.js +11 -0
- package/registry/demos/switch.tsx +5 -0
- package/registry/demos/tabs.d.ts +5 -0
- package/registry/demos/tabs.js +22 -0
- package/registry/demos/tabs.tsx +28 -0
- package/registry/demos/theme.d.ts +5 -0
- package/registry/demos/theme.js +11 -0
- package/registry/demos/theme.tsx +9 -0
- package/registry/demos/toast.d.ts +8 -0
- package/registry/demos/toast.js +29 -0
- package/registry/demos/toast.tsx +31 -0
- package/registry/demos/tooltip.d.ts +7 -0
- package/registry/demos/tooltip.js +15 -0
- package/registry/demos/tooltip.tsx +15 -0
- package/registry/dialog.json +218 -0
- package/registry/dropdown-menu.json +63 -0
- package/registry/empty-state.json +59 -0
- package/registry/field.json +66 -0
- package/registry/index.json +320 -0
- package/registry/kbd.json +38 -0
- package/registry/page-header.json +66 -0
- package/registry/popover.json +44 -0
- package/registry/progress.json +80 -0
- package/registry/radio-group.json +64 -0
- package/registry/separator.json +25 -0
- package/registry/skeleton.json +38 -0
- package/registry/slider.json +95 -0
- package/registry/slot.json +31 -0
- package/registry/spinner.json +45 -0
- package/registry/stat.json +66 -0
- package/registry/switch.json +53 -0
- package/registry/tabs.json +44 -0
- package/registry/theme.json +78 -0
- package/registry/toast.json +54 -0
- package/registry/tooltip.json +91 -0
- package/dist/chunk-5FRYKFTO.js.map +0 -1
- package/dist/chunk-QRG5RYEI.js.map +0 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tabs",
|
|
3
|
+
"type": "primitive",
|
|
4
|
+
"description": "`Tabs.Root/List/Trigger/Content` (variant underline · segmented · pills), `tabs` (the slots). Client (\"use client\").",
|
|
5
|
+
"files": [
|
|
6
|
+
{
|
|
7
|
+
"path": "tabs/index.ts",
|
|
8
|
+
"content": "/** @krizaka/ui/tabs — `Tabs.Root/List/Trigger/Content` (variant underline · segmented · pills), `tabs` (the slots). Client (\"use client\"). */\nexport { Tabs, tabs, TabsContent, TabsList, TabsRoot, type TabsRootProps, TabsTrigger, type TabsVariants } from \"./tabs\";\n"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"path": "tabs/tabs.tsx",
|
|
12
|
+
"content": "// Client (a context). Radix Tabs does the roles (tablist, tab, tabpanel), the aria-selected / aria-controls wiring,\n// the roving focus (arrows, Home, End) and automatic activation: this file only holds the three looks.\nimport { Tabs as TabsPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nexport const tabs = tv({\n slots: {\n root: \"flex gap-4 data-[orientation=horizontal]:flex-col data-[orientation=vertical]:flex-row\",\n list: \"flex shrink-0 data-[orientation=vertical]:flex-col\",\n trigger:\n \"inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap font-semibold transition-colors \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40\",\n content: \"kz-fade min-w-0 flex-1 rounded-lg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n },\n variants: {\n variant: {\n /* The section of a page: a line under the list, the active tab underlined in the accent. */\n underline: {\n list: \"gap-1 border-border-default data-[orientation=horizontal]:border-b data-[orientation=vertical]:border-r\",\n trigger:\n \"relative rounded-md px-3 py-2.5 text-sm text-fg-secondary hover:text-fg data-[state=active]:text-fg \" +\n \"after:absolute after:rounded-full after:bg-transparent data-[state=active]:after:bg-accent \" +\n \"data-[orientation=horizontal]:after:inset-x-2 data-[orientation=horizontal]:after:-bottom-px data-[orientation=horizontal]:after:h-0.5 \" +\n \"data-[orientation=vertical]:justify-start data-[orientation=vertical]:after:inset-y-1.5 data-[orientation=vertical]:after:-right-px data-[orientation=vertical]:after:w-0.5\",\n },\n /* A view switch: equal segments in a track, the active one filled. */\n segmented: {\n list: \"inline-grid auto-cols-fr grid-flow-col gap-1 self-start rounded-xl border border-border-default bg-surface-1 p-1 data-[orientation=vertical]:grid-flow-row\",\n trigger:\n \"min-h-8 rounded-lg px-3 py-1.5 text-xs text-fg-secondary hover:bg-surface-2 hover:text-fg \" +\n \"data-[state=active]:bg-accent data-[state=active]:text-on-accent data-[state=active]:shadow-sm data-[state=active]:hover:bg-accent\",\n },\n /* Pills that scroll sideways on a phone: a feed's sections. */\n pills: {\n list: \"gap-1.5 overflow-x-auto overscroll-x-contain [scrollbar-width:none] data-[orientation=horizontal]:pb-1\",\n trigger:\n \"h-9 rounded-full border border-border-default bg-surface-1 px-4 text-xs text-fg-secondary hover:border-border-strong hover:text-fg \" +\n \"data-[state=active]:border-accent data-[state=active]:bg-accent-soft data-[state=active]:text-fg\",\n },\n },\n },\n defaultVariants: { variant: \"underline\" },\n});\n\nexport type TabsVariants = VariantProps<typeof tabs>;\ntype Variant = NonNullable<TabsVariants[\"variant\"]>;\n\nconst VariantContext = React.createContext<Variant>(\"underline\");\n\nexport type TabsRootProps = React.ComponentProps<typeof TabsPrimitive.Root> & {\n /** underline (the sections of a page) · segmented (a view switch) · pills (a feed's sections). */\n variant?: TabsVariants[\"variant\"];\n};\n\n/** `value`/`defaultValue` + `onValueChange`, `orientation` (horizontal by default), `variant` for the list. */\nexport function TabsRoot({ variant = \"underline\", className, ...props }: TabsRootProps) {\n return (\n <VariantContext.Provider value={variant}>\n <TabsPrimitive.Root data-variant={variant} className={tabs({ variant }).root({ className })} {...props} />\n </VariantContext.Provider>\n );\n}\n\n/** The tabs. Name it (`aria-label`) when no heading says what it switches. */\nexport function TabsList({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.List>) {\n const variant = React.useContext(VariantContext);\n return <TabsPrimitive.List data-variant={variant} className={tabs({ variant }).list({ className })} {...props} />;\n}\n\nexport function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>) {\n const variant = React.useContext(VariantContext);\n return <TabsPrimitive.Trigger className={tabs({ variant }).trigger({ className })} {...props} />;\n}\n\nexport function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>) {\n return <TabsPrimitive.Content className={tabs().content({ className })} {...props} />;\n}\n\n/** `Tabs.Root/List/Trigger/Content` — `variant` underline · segmented · pills on the root. */\nexport const Tabs = { Root: TabsRoot, List: TabsList, Trigger: TabsTrigger, Content: TabsContent };\n"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"dependencies": [
|
|
16
|
+
"radix-ui@^1.7.0",
|
|
17
|
+
"tailwind-variants@^3.3.1"
|
|
18
|
+
],
|
|
19
|
+
"registryDependencies": [],
|
|
20
|
+
"demo": {
|
|
21
|
+
"path": "demos/tabs.tsx",
|
|
22
|
+
"content": "import { Tabs } from \"@krizaka/ui/tabs\";\n\nexport default function TabsDemo() {\n return (\n <Tabs.Root defaultValue=\"videos\">\n <Tabs.List aria-label=\"Profile\">\n <Tabs.Trigger value=\"videos\">Videos</Tabs.Trigger>\n <Tabs.Trigger value=\"stories\">Stories</Tabs.Trigger>\n <Tabs.Trigger value=\"collections\">Collections</Tabs.Trigger>\n <Tabs.Trigger value=\"about\" disabled>\n About\n </Tabs.Trigger>\n </Tabs.List>\n <Tabs.Content value=\"videos\" className=\"text-sm text-fg-secondary\">\n Twelve videos, newest first.\n </Tabs.Content>\n <Tabs.Content value=\"stories\" className=\"text-sm text-fg-secondary\">\n Stories of the last 24 hours.\n </Tabs.Content>\n <Tabs.Content value=\"collections\" className=\"text-sm text-fg-secondary\">\n Three public collections.\n </Tabs.Content>\n <Tabs.Content value=\"about\" className=\"text-sm text-fg-secondary\">\n About this channel.\n </Tabs.Content>\n </Tabs.Root>\n );\n}\n"
|
|
23
|
+
},
|
|
24
|
+
"props": [
|
|
25
|
+
{
|
|
26
|
+
"component": "TabsRoot",
|
|
27
|
+
"description": "`value`/`defaultValue` + `onValueChange`, `orientation` (horizontal by default), `variant` for the list.",
|
|
28
|
+
"props": [
|
|
29
|
+
{
|
|
30
|
+
"name": "variant",
|
|
31
|
+
"type": "\"underline\" | \"segmented\" | \"pills\"",
|
|
32
|
+
"default": "underline",
|
|
33
|
+
"description": "underline (the sections of a page) · segmented (a view switch) · pills (a feed's sections).",
|
|
34
|
+
"required": false
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"component": "TabsList",
|
|
40
|
+
"description": "The tabs. Name it (`aria-label`) when no heading says what it switches.",
|
|
41
|
+
"props": []
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "theme",
|
|
3
|
+
"type": "primitive",
|
|
4
|
+
"description": "`ThemeScript`, `ThemeProvider`, `useTheme`, `ThemeToggle`. Client (\"use client\").",
|
|
5
|
+
"files": [
|
|
6
|
+
{
|
|
7
|
+
"path": "theme/index.ts",
|
|
8
|
+
"content": "/** @krizaka/ui/theme — `ThemeScript`, `ThemeProvider`, `useTheme`, `ThemeToggle`. Client (\"use client\"). */\nexport {\n type Mode,\n type ThemeContextValue,\n ThemeProvider,\n type ThemeProviderProps,\n ThemeScript,\n ThemeToggle,\n type ThemeToggleProps,\n useTheme,\n} from \"./theme\";\n"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"path": "theme/theme.tsx",
|
|
12
|
+
"content": "// Client. ONE mechanism for the whole organisation: dark by default, `html.light` for light, `html.theme-<name>` for a\n// named theme, persisted in localStorage (`kz-theme` for the mode, `kz-theme-name` for the named theme).\nimport * as React from \"react\";\n\nimport { type ButtonVariants, buttonVariants } from \"../button/button\";\n\nexport type Mode = \"dark\" | \"light\" | \"system\";\n\nconst MODE_KEY = \"kz-theme\";\nconst NAME_KEY = \"kz-theme-name\";\nconst MODES: readonly Mode[] = [\"dark\", \"light\", \"system\"];\nconst DARK_QUERY = \"(prefers-color-scheme: dark)\";\n\nconst isMode = (value: unknown): value is Mode => MODES.includes(value as Mode);\n\n// Storage blocked (private mode, sandbox): the choice lives in memory for the page.\nconst memory = new Map<string, string>();\n\nfunction read(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return memory.get(key) ?? null;\n }\n}\n\nfunction write(key: string, value: string | null) {\n if (value === null) memory.delete(key);\n else memory.set(key, value);\n try {\n if (value === null) localStorage.removeItem(key);\n else localStorage.setItem(key, value);\n } catch {\n // storage blocked: memory holds it\n }\n}\n\n// The persisted choice is an external store: every provider and tab reads the same value, the server reads none.\nconst listeners = new Set<() => void>();\nconst notify = () => listeners.forEach((listener) => listener());\nfunction subscribe(listener: () => void) {\n listeners.add(listener);\n window.addEventListener(\"storage\", listener);\n return () => {\n listeners.delete(listener);\n window.removeEventListener(\"storage\", listener);\n };\n}\n\nfunction applyMode(mode: Mode) {\n const dark = window.matchMedia(DARK_QUERY).matches;\n document.documentElement.classList.toggle(\"light\", mode === \"light\" || (mode === \"system\" && !dark));\n}\n\nfunction applyName(previous: string | null, next: string | null) {\n const classes = document.documentElement.classList;\n if (previous) classes.remove(`theme-${previous}`);\n if (next) classes.add(`theme-${next}`);\n}\n\n/**\n * In <head>, before anything renders: applies the persisted mode and named theme, so the page never flashes.\n * `nonce` for a strict Content-Security-Policy.\n */\nexport function ThemeScript({\n nonce,\n}: {\n /** The nonce of a strict Content-Security-Policy (`script-src 'nonce-…'`). */\n nonce?: string;\n}) {\n const js =\n `(function(){try{var m=localStorage.getItem(\"${MODE_KEY}\")||\"system\";var n=localStorage.getItem(\"${NAME_KEY}\");` +\n `var d=matchMedia(\"${DARK_QUERY}\").matches;var c=document.documentElement.classList;` +\n `c.toggle(\"light\",m===\"light\"||(m===\"system\"&&!d));if(n)c.add(\"theme-\"+n)}catch(e){}})()`;\n return <script nonce={nonce} dangerouslySetInnerHTML={{ __html: js }} />;\n}\n\nexport type ThemeContextValue = {\n /** The chosen mode: dark, light or follow the system. */\n mode: Mode;\n setMode: (mode: Mode) => void;\n /** The named theme (`html.theme-<name>`), or null. */\n theme: string | null;\n setTheme: (theme: string | null) => void;\n};\n\nconst ThemeContext = React.createContext<ThemeContextValue | null>(null);\n\nexport type ThemeProviderProps = {\n children: React.ReactNode;\n /** The mode before anything is persisted. */\n defaultMode?: Mode;\n};\n\nexport function ThemeProvider({ children, defaultMode = \"system\" }: ThemeProviderProps) {\n // Before hydration the server knows nothing persisted (ThemeScript has already applied it to <html>).\n const stored = React.useSyncExternalStore(subscribe, () => read(MODE_KEY), () => null);\n const mode: Mode = isMode(stored) ? stored : defaultMode;\n const theme = React.useSyncExternalStore(subscribe, () => read(NAME_KEY), () => null);\n\n // \"system\" follows the operating system while the page is open.\n React.useEffect(() => {\n if (mode !== \"system\") return;\n const query = window.matchMedia(DARK_QUERY);\n const onChange = () => applyMode(\"system\");\n query.addEventListener?.(\"change\", onChange);\n return () => query.removeEventListener?.(\"change\", onChange);\n }, [mode]);\n\n const setMode = React.useCallback((next: Mode) => {\n write(MODE_KEY, next);\n applyMode(next);\n notify();\n }, []);\n\n const setTheme = React.useCallback((next: string | null) => {\n applyName(read(NAME_KEY), next);\n write(NAME_KEY, next);\n notify();\n }, []);\n\n const value = React.useMemo(() => ({ mode, setMode, theme, setTheme }), [mode, setMode, theme, setTheme]);\n return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;\n}\n\nexport function useTheme(): ThemeContextValue {\n const context = React.useContext(ThemeContext);\n if (!context) throw new Error(\"useTheme: wrap the app in <ThemeProvider>\");\n return context;\n}\n\nconst ICONS: Record<Mode, React.ReactNode> = {\n dark: <path d=\"M20 14.5A8 8 0 0 1 9.5 4a8 8 0 1 0 10.5 10.5Z\" />,\n light: (\n <>\n <circle cx=\"12\" cy=\"12\" r=\"4\" />\n <path d=\"M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4\" />\n </>\n ),\n system: (\n <>\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"12\" rx=\"2\" />\n <path d=\"M8 20h8M12 16v4\" />\n </>\n ),\n};\n\nexport type ThemeToggleProps = Omit<React.ComponentProps<\"button\">, \"onClick\" | \"children\"> &\n Pick<ButtonVariants, \"variant\" | \"shape\"> & {\n /** Any `Button` variant. */\n variant?: ButtonVariants[\"variant\"];\n /**\n * rounded · pill.\n * @default \"rounded\"\n */\n shape?: ButtonVariants[\"shape\"];\n /** The accessible name — a string, or one per current mode (e.g. `(mode) => t(\"theme.\" + mode)`). */\n label: string | ((mode: Mode) => string);\n };\n\n/**\n * An icon button (styled as `IconButton`, without its Slot) that cycles dark → light → system. Needs a\n * <ThemeProvider> above it. State: `data-mode`.\n */\nexport function ThemeToggle({ label, variant = \"ghost\", shape, className, ...props }: ThemeToggleProps) {\n const { mode, setMode } = useTheme();\n const next = MODES[(MODES.indexOf(mode) + 1) % MODES.length] as Mode;\n const name = typeof label === \"function\" ? label(mode) : label;\n return (\n <button\n type=\"button\"\n aria-label={name}\n title={name}\n data-mode={mode}\n onClick={() => setMode(next)}\n className={buttonVariants({ variant, shape, size: \"icon\", className })}\n {...props}\n >\n <svg viewBox=\"0 0 24 24\" className=\"h-5 w-5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n {ICONS[mode]}\n </svg>\n </button>\n );\n}\n"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"dependencies": [],
|
|
16
|
+
"registryDependencies": [
|
|
17
|
+
"button"
|
|
18
|
+
],
|
|
19
|
+
"demo": {
|
|
20
|
+
"path": "demos/theme.tsx",
|
|
21
|
+
"content": "import { ThemeProvider, ThemeToggle } from \"@krizaka/ui/theme\";\n\nexport default function ThemeDemo() {\n return (\n <ThemeProvider>\n <ThemeToggle label=\"Change the theme\" />\n </ThemeProvider>\n );\n}\n"
|
|
22
|
+
},
|
|
23
|
+
"props": [
|
|
24
|
+
{
|
|
25
|
+
"component": "ThemeProvider",
|
|
26
|
+
"description": "",
|
|
27
|
+
"props": [
|
|
28
|
+
{
|
|
29
|
+
"name": "defaultMode",
|
|
30
|
+
"type": "Mode",
|
|
31
|
+
"default": "system",
|
|
32
|
+
"description": "The mode before anything is persisted.",
|
|
33
|
+
"required": false
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"component": "ThemeScript",
|
|
39
|
+
"description": "In <head>, before anything renders: applies the persisted mode and named theme, so the page never flashes.\n`nonce` for a strict Content-Security-Policy.",
|
|
40
|
+
"props": [
|
|
41
|
+
{
|
|
42
|
+
"name": "nonce",
|
|
43
|
+
"type": "string",
|
|
44
|
+
"default": null,
|
|
45
|
+
"description": "The nonce of a strict Content-Security-Policy (`script-src 'nonce-…'`).",
|
|
46
|
+
"required": false
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"component": "ThemeToggle",
|
|
52
|
+
"description": "An icon button (styled as `IconButton`, without its Slot) that cycles dark → light → system. Needs a\n<ThemeProvider> above it. State: `data-mode`.",
|
|
53
|
+
"props": [
|
|
54
|
+
{
|
|
55
|
+
"name": "label",
|
|
56
|
+
"type": "string | ((mode: Mode) => string)",
|
|
57
|
+
"default": null,
|
|
58
|
+
"description": "The accessible name — a string, or one per current mode (e.g. `(mode) => t(\"theme.\" + mode)`).",
|
|
59
|
+
"required": true
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "shape",
|
|
63
|
+
"type": "\"rounded\" | \"pill\"",
|
|
64
|
+
"default": "rounded",
|
|
65
|
+
"description": "rounded · pill.",
|
|
66
|
+
"required": false
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "variant",
|
|
70
|
+
"type": "\"danger\" | \"primary\" | \"secondary\" | \"outline\" | \"ghost\"",
|
|
71
|
+
"default": "ghost",
|
|
72
|
+
"description": "Any `Button` variant.",
|
|
73
|
+
"required": false
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "toast",
|
|
3
|
+
"type": "primitive",
|
|
4
|
+
"description": "`Toaster` (sonner, styled by roles), `toast`. Client (\"use client\").",
|
|
5
|
+
"files": [
|
|
6
|
+
{
|
|
7
|
+
"path": "toast/index.ts",
|
|
8
|
+
"content": "/** @krizaka/ui/toast — `Toaster` (sonner, styled by roles), `toast`. Client (\"use client\"). */\nexport { type ExternalToast, toast, toastClassNames, Toaster, type ToasterProps } from \"./toast\";\n"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"path": "toast/toast.tsx",
|
|
12
|
+
"content": "// Client. sonner does the queue, the stacking, the swipe, the timers, the live region and the hotkey; the look is\n// ours: sonner's own styling is off (`unstyled`) and every part reads a role. The status tones are soft (a tinted\n// border and a coloured icon, the text stays `text-fg`): the invariant status tokens are too light for small text.\nimport { Toaster as Sonner, type ToasterProps as SonnerProps } from \"sonner\";\n\nimport { cn } from \"../cn\";\n\nexport type { ExternalToast } from \"sonner\";\nexport { toast } from \"sonner\";\n\nconst tone = (color: string) => cn(\"border-l-4\", color);\n\nexport const toastClassNames = {\n toast:\n \"group pointer-events-auto relative flex w-full items-start gap-3 rounded-xl border border-border-default bg-surface-2 p-4 pr-10 \" +\n \"text-sm text-fg shadow-lg\",\n default: \"\",\n title: \"font-semibold text-fg\",\n description: \"mt-0.5 text-xs text-fg-secondary\",\n content: \"flex min-w-0 flex-1 flex-col\",\n icon: \"mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center [&>svg]:h-4 [&>svg]:w-4\",\n success: tone(\"border-l-success [&_[data-icon]]:text-success\"),\n warning: tone(\"border-l-warning [&_[data-icon]]:text-warning\"),\n error: tone(\"border-l-danger [&_[data-icon]]:text-danger\"),\n info: tone(\"border-l-info [&_[data-icon]]:text-info\"),\n loading: \"\",\n actionButton:\n \"ml-auto shrink-0 self-center rounded-lg bg-accent px-3 py-1.5 text-xs font-semibold text-on-accent hover:bg-accent-hover \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n cancelButton:\n \"shrink-0 self-center rounded-lg px-3 py-1.5 text-xs font-semibold text-fg-secondary hover:bg-surface-3 hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n closeButton:\n \"absolute right-2 top-2 flex h-7 w-7 items-center justify-center rounded-full text-fg-secondary hover:bg-surface-3 hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring [&>svg]:h-3.5 [&>svg]:w-3.5\",\n} satisfies NonNullable<NonNullable<SonnerProps[\"toastOptions\"]>[\"classNames\"]>;\n\nexport type ToasterProps = Omit<SonnerProps, \"richColors\" | \"theme\" | \"containerAriaLabel\" | \"toastOptions\"> & {\n /** The accessible name of the notifications region (sonner's `containerAriaLabel`) — passed translated. */\n label: string;\n /** The accessible name of each toast's close button — passed translated. */\n closeLabel: string;\n /** Extra options for every toast (duration, a className); the class names are merged with the platform's. */\n toastOptions?: Omit<NonNullable<SonnerProps[\"toastOptions\"]>, \"unstyled\" | \"closeButtonAriaLabel\">;\n};\n\n/** Mount once, in the root layout. Then call `toast()`, `toast.success/warning/error/info`, `toast.custom(…)`. */\nexport function Toaster({ label, closeLabel, position = \"bottom-right\", closeButton = true, toastOptions, ...props }: ToasterProps) {\n const extra = toastOptions?.classNames ?? {};\n const classNames = Object.fromEntries(\n Object.entries(toastClassNames).map(([key, value]) => [key, cn(value, extra[key as keyof typeof extra])]),\n ) as typeof toastClassNames;\n return (\n <Sonner\n position={position}\n closeButton={closeButton}\n containerAriaLabel={label}\n richColors={false}\n toastOptions={{ ...toastOptions, unstyled: true, closeButtonAriaLabel: closeLabel, classNames }}\n {...props}\n />\n );\n}\n"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"dependencies": [
|
|
16
|
+
"sonner@^2.0.8"
|
|
17
|
+
],
|
|
18
|
+
"registryDependencies": [
|
|
19
|
+
"cn"
|
|
20
|
+
],
|
|
21
|
+
"demo": {
|
|
22
|
+
"path": "demos/toast.tsx",
|
|
23
|
+
"content": "\"use client\";\n\nimport { Button } from \"@krizaka/ui/button\";\nimport { toast, Toaster } from \"@krizaka/ui/toast\";\nimport { useEffect } from \"react\";\n\n// Each toaster has its own `id`: the toasts of this demo stay in this demo's toaster.\nconst toasterId = \"tones\";\nconst show = () => {\n toast(\"Draft saved\", { toasterId, description: \"Autosaved a moment ago.\" });\n toast.success(\"Payment received\", { toasterId, description: \"€12.00 from @maya.\" });\n toast.warning(\"Your balance is low\", { toasterId, description: \"Two tips left before a top-up.\" });\n toast.error(\"Upload failed\", { toasterId, description: \"The connection dropped at 64 %.\", action: { label: \"Retry\", onClick: () => {} } });\n};\n\n/** `defaultOpen`: the toasts show at once and stay (the screenshot); otherwise the button shows them. */\nexport default function ToastDemo({ defaultOpen = false }: { defaultOpen?: boolean }) {\n useEffect(() => {\n if (!defaultOpen) return;\n toast.dismiss();\n show();\n }, [defaultOpen]);\n return (\n <>\n <Toaster id={toasterId} label=\"Notifications\" closeLabel=\"Dismiss\" position=\"bottom-right\" expand visibleToasts={4} duration={defaultOpen ? Number.POSITIVE_INFINITY : undefined} />\n <Button variant=\"outline\" onClick={show}>\n Show again\n </Button>\n </>\n );\n}\n"
|
|
24
|
+
},
|
|
25
|
+
"props": [
|
|
26
|
+
{
|
|
27
|
+
"component": "Toaster",
|
|
28
|
+
"description": "Mount once, in the root layout. Then call `toast()`, `toast.success/warning/error/info`, `toast.custom(…)`.",
|
|
29
|
+
"props": [
|
|
30
|
+
{
|
|
31
|
+
"name": "closeLabel",
|
|
32
|
+
"type": "string",
|
|
33
|
+
"default": null,
|
|
34
|
+
"description": "The accessible name of each toast's close button — passed translated.",
|
|
35
|
+
"required": true
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "label",
|
|
39
|
+
"type": "string",
|
|
40
|
+
"default": null,
|
|
41
|
+
"description": "The accessible name of the notifications region (sonner's `containerAriaLabel`) — passed translated.",
|
|
42
|
+
"required": true
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "toastOptions",
|
|
46
|
+
"type": "Omit<ToastOptions, \"unstyled\" | \"closeButtonAriaLabel\">",
|
|
47
|
+
"default": null,
|
|
48
|
+
"description": "Extra options for every toast (duration, a className); the class names are merged with the platform's.",
|
|
49
|
+
"required": false
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tooltip",
|
|
3
|
+
"type": "primitive",
|
|
4
|
+
"description": "`Tooltip`, `TooltipProvider`. Client (\"use client\").",
|
|
5
|
+
"files": [
|
|
6
|
+
{
|
|
7
|
+
"path": "floating.ts",
|
|
8
|
+
"content": "// The one surface of everything that floats next to its trigger (popover, menu, tooltip): a raised surface, a border,\n// the large shadow and the `kz-pop` entrance from the side Radix measured (`--radix-*-transform-origin`).\nexport const floating =\n \"kz-pop z-50 rounded-lg border border-border-default bg-surface-2 text-fg shadow-lg outline-hidden\";\n"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"path": "tooltip/index.ts",
|
|
12
|
+
"content": "/** @krizaka/ui/tooltip — `Tooltip`, `TooltipProvider`. Client (\"use client\"). */\nexport { Tooltip, type TooltipProps, TooltipProvider } from \"./tooltip\";\n"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"path": "tooltip/tooltip.tsx",
|
|
16
|
+
"content": "// Client (a portal). A short label for a control, on hover after `delayDuration` and on keyboard focus at once;\n// Escape dismisses it. It names nothing: the control keeps its own accessible name (an IconButton's `label`).\nimport { Tooltip as TooltipPrimitive } from \"radix-ui\";\nimport type * as React from \"react\";\n\nimport { cn } from \"../cn\";\nimport { floating } from \"../floating\";\n\n/** Optional, once near the root: shares `delayDuration` and the skip delay between tooltips. */\nexport const TooltipProvider = TooltipPrimitive.Provider;\n\nexport type TooltipProps = Omit<React.ComponentProps<typeof TooltipPrimitive.Content>, \"content\" | \"children\"> & {\n /** What the tooltip says — passed translated. */\n content: React.ReactNode;\n /** The trigger, rendered as is (`asChild`): a focusable element. */\n children: React.ReactNode;\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. */\n onOpenChange?: (open: boolean) => void;\n /** Hover delay in ms before it opens (300 by default). */\n delayDuration?: number;\n /** The element the portal renders into (default: document.body). */\n container?: HTMLElement | null;\n};\n\nexport function Tooltip({\n content,\n children,\n open,\n defaultOpen,\n onOpenChange,\n delayDuration = 300,\n container,\n sideOffset = 6,\n collisionPadding = 8,\n className,\n ...props\n}: TooltipProps) {\n return (\n <TooltipPrimitive.Provider delayDuration={delayDuration}>\n <TooltipPrimitive.Root open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>\n <TooltipPrimitive.Trigger asChild>{children}</TooltipPrimitive.Trigger>\n <TooltipPrimitive.Portal container={container}>\n <TooltipPrimitive.Content\n sideOffset={sideOffset}\n collisionPadding={collisionPadding}\n className={cn(floating, \"max-w-xs px-2.5 py-1.5 text-xs font-medium origin-(--radix-tooltip-content-transform-origin)\", className)}\n {...props}\n >\n {content}\n </TooltipPrimitive.Content>\n </TooltipPrimitive.Portal>\n </TooltipPrimitive.Root>\n </TooltipPrimitive.Provider>\n );\n}\n"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"dependencies": [
|
|
20
|
+
"radix-ui@^1.7.0"
|
|
21
|
+
],
|
|
22
|
+
"registryDependencies": [
|
|
23
|
+
"cn"
|
|
24
|
+
],
|
|
25
|
+
"demo": {
|
|
26
|
+
"path": "demos/tooltip.tsx",
|
|
27
|
+
"content": "import { IconButton } from \"@krizaka/ui/button\";\nimport { Tooltip } from \"@krizaka/ui/tooltip\";\n\nexport default function TooltipDemo({ defaultOpen = false }: { defaultOpen?: boolean }) {\n return (\n <Tooltip content=\"Copy the link\" defaultOpen={defaultOpen}>\n <IconButton label=\"Copy\" variant=\"outline\">\n <svg viewBox=\"0 0 24 24\" className=\"h-4 w-4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <rect x=\"9\" y=\"9\" width=\"12\" height=\"12\" rx=\"2\" />\n <path d=\"M5 15V5a2 2 0 0 1 2-2h10\" />\n </svg>\n </IconButton>\n </Tooltip>\n );\n}\n"
|
|
28
|
+
},
|
|
29
|
+
"props": [
|
|
30
|
+
{
|
|
31
|
+
"component": "Tooltip",
|
|
32
|
+
"description": "",
|
|
33
|
+
"props": [
|
|
34
|
+
{
|
|
35
|
+
"name": "children",
|
|
36
|
+
"type": "ReactNode",
|
|
37
|
+
"default": null,
|
|
38
|
+
"description": "The trigger, rendered as is (`asChild`): a focusable element.",
|
|
39
|
+
"required": true
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "content",
|
|
43
|
+
"type": "ReactNode",
|
|
44
|
+
"default": null,
|
|
45
|
+
"description": "What the tooltip says — passed translated.",
|
|
46
|
+
"required": true
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "container",
|
|
50
|
+
"type": "HTMLElement | null",
|
|
51
|
+
"default": null,
|
|
52
|
+
"description": "The element the portal renders into (default: document.body).",
|
|
53
|
+
"required": false
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "defaultOpen",
|
|
57
|
+
"type": "boolean",
|
|
58
|
+
"default": null,
|
|
59
|
+
"description": "Open at first, uncontrolled.",
|
|
60
|
+
"required": false
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "delayDuration",
|
|
64
|
+
"type": "number",
|
|
65
|
+
"default": "300",
|
|
66
|
+
"description": "Hover delay in ms before it opens (300 by default).",
|
|
67
|
+
"required": false
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "onOpenChange",
|
|
71
|
+
"type": "((open: boolean) => void)",
|
|
72
|
+
"default": null,
|
|
73
|
+
"description": "Called when it opens or closes.",
|
|
74
|
+
"required": false
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"name": "open",
|
|
78
|
+
"type": "boolean",
|
|
79
|
+
"default": null,
|
|
80
|
+
"description": "Open or closed (controlled), with `onOpenChange`.",
|
|
81
|
+
"required": false
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"component": "TooltipProvider",
|
|
87
|
+
"description": "Optional, once near the root: shares `delayDuration` and the skip delay between tooltips.",
|
|
88
|
+
"props": []
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/dialog/dialog.tsx"],"sourcesContent":["// Client (a portal). Radix does the focus trap, the scroll lock, Escape, the outside click, aria-modal, the\n// aria-labelledby / aria-describedby wiring and the focus return: this file only holds the shape.\nimport { Dialog as DialogPrimitive } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nimport { IconButton } from \"../button/button\";\n\nexport const dialog = tv({\n slots: {\n overlay: \"kz-overlay fixed inset-0 z-50 bg-overlay backdrop-blur-xs\",\n content:\n \"kz-dialog fixed z-50 flex flex-col overflow-hidden border border-border-default bg-surface-1 text-fg shadow-lg focus:outline-hidden\",\n close: \"absolute right-3 top-3 h-9 w-9\",\n header: \"flex flex-col gap-1 px-5 pb-2 pr-14 pt-5\",\n title: \"text-base font-bold text-fg\",\n description: \"text-sm text-fg-secondary\",\n body: \"min-h-0 flex-1 overflow-y-auto px-5 pb-5\",\n footer:\n \"flex flex-wrap items-center justify-end gap-2 border-t border-border-subtle px-5 py-4 pb-[max(1rem,env(safe-area-inset-bottom))]\",\n },\n variants: {\n placement: {\n center: { content: \"left-1/2 top-1/2 max-h-[85dvh] w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 rounded-xl\" },\n /* A sheet: anchored at the bottom on a phone, centred from `sm` up. */\n bottom: {\n content:\n \"inset-x-0 bottom-0 max-h-[92dvh] rounded-t-[1.75rem] sm:bottom-auto sm:left-1/2 sm:top-1/2 sm:w-full \" +\n \"sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-[1.75rem]\",\n },\n right: { content: \"inset-y-0 right-0 h-full w-full max-w-md rounded-none border-y-0 border-r-0\" },\n },\n size: { sm: { content: \"sm:max-w-sm\" }, md: { content: \"sm:max-w-md\" }, lg: { content: \"sm:max-w-2xl\" } },\n },\n defaultVariants: { placement: \"center\", size: \"md\" },\n});\nconst s = dialog();\n\nexport type DialogVariants = VariantProps<typeof dialog>;\n\nfunction CloseIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" className=\"h-4 w-4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n );\n}\n\ntype CloseButtonProps =\n | {\n /** The accessible name of the close button — passed translated. */\n closeLabel: string;\n /** Leaves the close button out (the content brings its own way out, or none: see `dismissible`). */\n hideClose?: false;\n }\n | { closeLabel?: string; hideClose: true };\n\nexport type DialogContentProps = React.ComponentProps<typeof DialogPrimitive.Content> &\n DialogVariants &\n CloseButtonProps & {\n /**\n * `false`: Escape and a click outside do not close it (a gate the user must answer: age, terms). It still closes\n * through `open` / `onOpenChange` and any `Dialog.Close` inside. Default `true`.\n */\n dismissible?: boolean;\n /** The element the portal renders into (default: document.body). */\n container?: HTMLElement | null;\n };\n\nconst prevent = (event: Event) => event.preventDefault();\n\n/** The dialog itself, portalled over a dimmed overlay, with a close button. Give it a `Dialog.Title`. */\nexport function DialogContent({\n placement,\n size,\n closeLabel,\n hideClose,\n dismissible = true,\n container,\n className,\n children,\n onEscapeKeyDown,\n onPointerDownOutside,\n onInteractOutside,\n ...props\n}: DialogContentProps) {\n const v = dialog({ placement, size });\n return (\n <DialogPrimitive.Portal container={container}>\n <DialogPrimitive.Overlay className={v.overlay()} />\n <DialogPrimitive.Content\n data-placement={placement ?? \"center\"}\n data-dismissible={dismissible ? undefined : \"false\"}\n className={v.content({ className })}\n onEscapeKeyDown={dismissible ? onEscapeKeyDown : chain(onEscapeKeyDown, prevent)}\n onPointerDownOutside={dismissible ? onPointerDownOutside : chain(onPointerDownOutside, prevent)}\n onInteractOutside={dismissible ? onInteractOutside : chain(onInteractOutside, prevent)}\n {...props}\n >\n {children}\n {!hideClose && (\n <DialogPrimitive.Close asChild>\n <IconButton label={closeLabel as string} variant=\"ghost\" shape=\"pill\" className={v.close()}>\n <CloseIcon />\n </IconButton>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n );\n}\n\nfunction chain<E extends Event>(handler: ((event: E) => void) | undefined, then: (event: E) => void) {\n return (event: E) => {\n handler?.(event);\n then(event);\n };\n}\n\nexport function DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.header({ className })} {...props} />;\n}\n\nexport function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {\n return <DialogPrimitive.Title className={s.title({ className })} {...props} />;\n}\n\nexport function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {\n return <DialogPrimitive.Description className={s.description({ className })} {...props} />;\n}\n\nexport function DialogBody({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.body({ className })} {...props} />;\n}\n\nexport function DialogFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.footer({ className })} {...props} />;\n}\n\n/** `Dialog.Root/Trigger/Content/Header/Title/Description/Body/Footer/Close`. `open`/`defaultOpen` + `onOpenChange`. */\nexport const Dialog = {\n Root: DialogPrimitive.Root,\n Trigger: DialogPrimitive.Trigger,\n Close: DialogPrimitive.Close,\n Content: DialogContent,\n Header: DialogHeader,\n Title: DialogTitle,\n Description: DialogDescription,\n Body: DialogBody,\n Footer: DialogFooter,\n};\n\n/** `Omit` that keeps the close-button union (`closeLabel` required unless `hideClose`). */\ntype DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;\n\nexport type SheetProps = DistributiveOmit<DialogContentProps, \"placement\">;\n\n/** A sheet is a dialog anchored at the bottom (centred from `sm` up): `Dialog.Content placement=\"bottom\"`. */\nexport function Sheet(props: SheetProps) {\n return <DialogContent placement=\"bottom\" {...props} />;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/button/button.tsx"],"sourcesContent":["// Server-safe: no hook, no context. `buttonVariants` styles a <Link> or a Server Component without the component.\nimport { Slot } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nexport const buttonVariants = tv({\n base:\n \"inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap font-semibold transition-all \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface-0 \" +\n \"disabled:pointer-events-none disabled:opacity-40 data-loading:cursor-progress active:scale-[0.98]\",\n variants: {\n variant: {\n primary: \"bg-accent text-on-accent shadow-sm hover:bg-accent-hover\",\n secondary: \"border border-border-default bg-surface-2 text-fg hover:border-border-strong hover:bg-surface-3\",\n outline: \"border border-border-default bg-transparent text-fg hover:border-accent hover:text-accent\",\n ghost: \"text-fg-secondary hover:bg-surface-2 hover:text-fg\",\n // The danger token is invariant and too light for small text on a light surface (3.6:1): the label stays a\n // text role, the border and the tint carry the danger.\n danger: \"border border-danger/50 bg-danger/10 text-fg hover:border-danger hover:bg-danger/20\",\n },\n size: { sm: \"h-8 px-3 text-xs\", md: \"h-10 px-4 text-sm\", lg: \"h-12 px-6 text-sm\", icon: \"h-10 w-10\" },\n shape: { rounded: \"rounded-lg\", pill: \"rounded-full\" },\n },\n defaultVariants: { variant: \"secondary\", size: \"md\", shape: \"rounded\" },\n});\n\nexport type ButtonVariants = VariantProps<typeof buttonVariants>;\n\nexport type ButtonProps = React.ComponentProps<\"button\"> &\n ButtonVariants & {\n /** Renders the child (e.g. a <Link>) instead of the <button>, with props, classes and ref merged. */\n asChild?: boolean;\n /** Disables and signals the wait (aria-busy, data-loading) — the product adds a spinner if it wants one. */\n loading?: boolean;\n };\n\nexport function Button({ asChild, loading, variant, size, shape, className, disabled, ...props }: ButtonProps) {\n const Comp = asChild ? Slot.Root : \"button\";\n return (\n <Comp\n type={asChild ? undefined : \"button\"}\n aria-busy={loading || undefined}\n data-loading={loading ? \"\" : undefined}\n data-variant={variant ?? \"secondary\"}\n disabled={asChild ? undefined : disabled || loading}\n aria-disabled={asChild && (disabled || loading) ? true : undefined}\n className={buttonVariants({ variant, size, shape, className })}\n {...props}\n />\n );\n}\n\nexport type IconButtonProps = Omit<ButtonProps, \"size\"> & {\n /** The accessible name (and tooltip) of the button — required, passed translated. */\n label: string;\n size?: ButtonProps[\"size\"];\n};\n\n/** An icon-only button: `label` is required, it is its accessible name. */\nexport function IconButton({ label, size = \"icon\", ...props }: IconButtonProps) {\n return <Button aria-label={label} title={label} size={size} {...props} />;\n}\n"],"mappings":";;;AACA,SAAS,YAAY;AAErB,SAAS,UAA6B;AAoClC;AAlCG,IAAM,iBAAiB,GAAG;AAAA,EAC/B,MACE;AAAA,EAGF,UAAU;AAAA,IACR,SAAS;AAAA,MACP,SAAS;AAAA,MACT,WAAW;AAAA,MACX,SAAS;AAAA,MACT,OAAO;AAAA;AAAA;AAAA,MAGP,QAAQ;AAAA,IACV;AAAA,IACA,MAAM,EAAE,IAAI,oBAAoB,IAAI,qBAAqB,IAAI,qBAAqB,MAAM,YAAY;AAAA,IACpG,OAAO,EAAE,SAAS,cAAc,MAAM,eAAe;AAAA,EACvD;AAAA,EACA,iBAAiB,EAAE,SAAS,aAAa,MAAM,MAAM,OAAO,UAAU;AACxE,CAAC;AAYM,SAAS,OAAO,EAAE,SAAS,SAAS,SAAS,MAAM,OAAO,WAAW,UAAU,GAAG,MAAM,GAAgB;AAC7G,QAAM,OAAO,UAAU,KAAK,OAAO;AACnC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,UAAU,SAAY;AAAA,MAC5B,aAAW,WAAW;AAAA,MACtB,gBAAc,UAAU,KAAK;AAAA,MAC7B,gBAAc,WAAW;AAAA,MACzB,UAAU,UAAU,SAAY,YAAY;AAAA,MAC5C,iBAAe,YAAY,YAAY,WAAW,OAAO;AAAA,MACzD,WAAW,eAAe,EAAE,SAAS,MAAM,OAAO,UAAU,CAAC;AAAA,MAC5D,GAAG;AAAA;AAAA,EACN;AAEJ;AASO,SAAS,WAAW,EAAE,OAAO,OAAO,QAAQ,GAAG,MAAM,GAAoB;AAC9E,SAAO,oBAAC,UAAO,cAAY,OAAO,OAAO,OAAO,MAAa,GAAG,OAAO;AACzE;","names":[]}
|