@nphuonha/ui-kit 0.0.0-stage → 0.3.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.
Files changed (113) hide show
  1. package/CONVENTIONS.md +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +114 -2
  4. package/dist/components/ui/_shared/icon-provider.d.ts +18 -0
  5. package/dist/components/ui/_shared/icon-provider.js +26 -0
  6. package/dist/components/ui/alert/alert-variants.d.ts +5 -0
  7. package/dist/components/ui/alert/alert-variants.js +13 -0
  8. package/dist/components/ui/alert/alert.d.ts +9 -0
  9. package/dist/components/ui/alert/alert.js +13 -0
  10. package/dist/components/ui/alert/index.d.ts +2 -0
  11. package/dist/components/ui/alert/index.js +2 -0
  12. package/dist/components/ui/badge/badge-variants.d.ts +5 -0
  13. package/dist/components/ui/badge/badge-variants.js +16 -0
  14. package/dist/components/ui/badge/badge.d.ts +6 -0
  15. package/dist/components/ui/badge/badge.js +6 -0
  16. package/dist/components/ui/badge/index.d.ts +2 -0
  17. package/dist/components/ui/badge/index.js +2 -0
  18. package/dist/components/ui/bottom-nav/bottom-nav.d.ts +20 -0
  19. package/dist/components/ui/bottom-nav/bottom-nav.js +36 -0
  20. package/dist/components/ui/bottom-nav/index.d.ts +1 -0
  21. package/dist/components/ui/bottom-nav/index.js +1 -0
  22. package/dist/components/ui/button/button-variants.d.ts +7 -0
  23. package/dist/components/ui/button/button-variants.js +29 -0
  24. package/dist/components/ui/button/button.d.ts +10 -0
  25. package/dist/components/ui/button/button.js +12 -0
  26. package/dist/components/ui/button/index.d.ts +2 -0
  27. package/dist/components/ui/button/index.js +2 -0
  28. package/dist/components/ui/card/card.d.ts +7 -0
  29. package/dist/components/ui/card/card.js +20 -0
  30. package/dist/components/ui/card/index.d.ts +1 -0
  31. package/dist/components/ui/card/index.js +1 -0
  32. package/dist/components/ui/checkbox/checkbox.d.ts +8 -0
  33. package/dist/components/ui/checkbox/checkbox.js +18 -0
  34. package/dist/components/ui/checkbox/index.d.ts +1 -0
  35. package/dist/components/ui/checkbox/index.js +1 -0
  36. package/dist/components/ui/desktop-nav/desktop-nav.d.ts +18 -0
  37. package/dist/components/ui/desktop-nav/desktop-nav.js +37 -0
  38. package/dist/components/ui/desktop-nav/index.d.ts +1 -0
  39. package/dist/components/ui/desktop-nav/index.js +1 -0
  40. package/dist/components/ui/dialog/dialog.d.ts +15 -0
  41. package/dist/components/ui/dialog/dialog.js +10 -0
  42. package/dist/components/ui/dialog/index.d.ts +1 -0
  43. package/dist/components/ui/dialog/index.js +1 -0
  44. package/dist/components/ui/field/field.d.ts +11 -0
  45. package/dist/components/ui/field/field.js +22 -0
  46. package/dist/components/ui/field/index.d.ts +1 -0
  47. package/dist/components/ui/field/index.js +1 -0
  48. package/dist/components/ui/icon-button/icon-button.d.ts +7 -0
  49. package/dist/components/ui/icon-button/icon-button.js +7 -0
  50. package/dist/components/ui/icon-button/index.d.ts +1 -0
  51. package/dist/components/ui/icon-button/index.js +1 -0
  52. package/dist/components/ui/input/index.d.ts +1 -0
  53. package/dist/components/ui/input/index.js +1 -0
  54. package/dist/components/ui/input/input.d.ts +5 -0
  55. package/dist/components/ui/input/input.js +6 -0
  56. package/dist/components/ui/label/index.d.ts +1 -0
  57. package/dist/components/ui/label/index.js +1 -0
  58. package/dist/components/ui/label/label.d.ts +5 -0
  59. package/dist/components/ui/label/label.js +6 -0
  60. package/dist/components/ui/popover/index.d.ts +1 -0
  61. package/dist/components/ui/popover/index.js +1 -0
  62. package/dist/components/ui/popover/popover.d.ts +14 -0
  63. package/dist/components/ui/popover/popover.js +7 -0
  64. package/dist/components/ui/radio-group/index.d.ts +1 -0
  65. package/dist/components/ui/radio-group/index.js +1 -0
  66. package/dist/components/ui/radio-group/radio-group.d.ts +17 -0
  67. package/dist/components/ui/radio-group/radio-group.js +17 -0
  68. package/dist/components/ui/select/index.d.ts +1 -0
  69. package/dist/components/ui/select/index.js +1 -0
  70. package/dist/components/ui/select/select.d.ts +21 -0
  71. package/dist/components/ui/select/select.js +10 -0
  72. package/dist/components/ui/separator/index.d.ts +1 -0
  73. package/dist/components/ui/separator/index.js +1 -0
  74. package/dist/components/ui/separator/separator.d.ts +6 -0
  75. package/dist/components/ui/separator/separator.js +5 -0
  76. package/dist/components/ui/sheet/index.d.ts +1 -0
  77. package/dist/components/ui/sheet/index.js +1 -0
  78. package/dist/components/ui/sheet/sheet.d.ts +15 -0
  79. package/dist/components/ui/sheet/sheet.js +15 -0
  80. package/dist/components/ui/spinner/index.d.ts +1 -0
  81. package/dist/components/ui/spinner/index.js +1 -0
  82. package/dist/components/ui/spinner/spinner.d.ts +5 -0
  83. package/dist/components/ui/spinner/spinner.js +8 -0
  84. package/dist/components/ui/switch/index.d.ts +1 -0
  85. package/dist/components/ui/switch/index.js +1 -0
  86. package/dist/components/ui/switch/switch.d.ts +7 -0
  87. package/dist/components/ui/switch/switch.js +15 -0
  88. package/dist/components/ui/tabs/index.d.ts +1 -0
  89. package/dist/components/ui/tabs/index.js +1 -0
  90. package/dist/components/ui/tabs/tabs.d.ts +14 -0
  91. package/dist/components/ui/tabs/tabs.js +7 -0
  92. package/dist/components/ui/textarea/index.d.ts +1 -0
  93. package/dist/components/ui/textarea/index.js +1 -0
  94. package/dist/components/ui/textarea/textarea.d.ts +5 -0
  95. package/dist/components/ui/textarea/textarea.js +6 -0
  96. package/dist/components/ui/toast/index.d.ts +1 -0
  97. package/dist/components/ui/toast/index.js +1 -0
  98. package/dist/components/ui/toast/toast.d.ts +19 -0
  99. package/dist/components/ui/toast/toast.js +20 -0
  100. package/dist/components/ui/tooltip/index.d.ts +1 -0
  101. package/dist/components/ui/tooltip/index.js +1 -0
  102. package/dist/components/ui/tooltip/tooltip.d.ts +12 -0
  103. package/dist/components/ui/tooltip/tooltip.js +9 -0
  104. package/dist/index.d.ts +29 -0
  105. package/dist/index.js +31 -0
  106. package/dist/lib/color.d.ts +16 -0
  107. package/dist/lib/color.js +118 -0
  108. package/dist/lib/utils.d.ts +7 -0
  109. package/dist/lib/utils.js +5 -0
  110. package/dist/styles/base.css +212 -0
  111. package/dist/styles/theme.css +121 -0
  112. package/dist/styles.css +5 -0
  113. package/package.json +203 -4
package/CONVENTIONS.md ADDED
@@ -0,0 +1,38 @@
1
+ # UI Kit Conventions (Solid / Clean / Flat / Modern)
2
+
3
+ Single-suite, not-shadcn component kit: **Base UI** for behavior/a11y + owned tokens, styles, and a consistent **prop-driven** API. Visual direction: solid surfaces, clean hierarchy, flat-first depth, modern rounded shapes, vibrant-but-customizable accent (inspired by M3 Expressive, not M3 Expressive).
4
+
5
+ ## Foundations
6
+ - **Semantic tokens only** in components (no app-specific variables, no hardcoded brand paints). Core contract lives in `src/styles/theme.css` and is mapped in `src/styles/base.css`.
7
+ - **Accent is customizable** via CSS vars (`--primary`, `--primary-foreground`, `--primary-hover/active`, `--primary-soft*`, `--ring`). Use `generateAccentVars`/`accentStyle` from `@nphuonha/ui-kit` to derive a palette from one color (light/dark).
8
+ - **Scope**: wrap with `data-ui="kit"` (put on `<html>`/portal root so overlay portals inherit tokens). Dark mode via `.dark`.
9
+
10
+ ## Visual
11
+ - **Flat-first, softly curved**: separate surfaces with tonal solid fills; use a **top-lit rim + micro vertical gradient** for convexity instead of full outlines.
12
+ - `surface-curved` — cards, dialogs, popovers, primary/danger buttons.
13
+ - `surface-curved-soft` — secondary buttons, tabs, alerts, badges, switch tracks.
14
+ - `surface-curved-pill` — switch thumbs and pill controls.
15
+ - `surface-inset` — native fields (input/textarea/select), where `::before` is unavailable.
16
+ - Lighting tokens: `--light-top`, `--light-sheen`, `--light-bottom`, `--light-rim` (defined per light/dark theme).
17
+ - **Shadows only lift, never outline**: keep subtle outer shadows for overlays (`--shadow-overlay`) and raised cards/buttons (`--shadow-soft`/`--shadow-raised`); the curved utilities only add inset edge light.
18
+ - **Shape** via tokens: `--radius-control` (~12px), `--radius-card` (~20px), `--radius-overlay`, `--radius-field`; pill (`rounded-pill`) for actions and switch thumbs.
19
+ - **Typography**: tokenized (`--font-body`), clear hierarchy.
20
+ - **Motion**: restrained 150–220ms, `--ease-out-ui`, CSS transitions for feedback/enter-exit; **no bouncy springs** by default. Honor `prefers-reduced-motion` (global guard in `base.css`).
21
+
22
+ ## Component API
23
+ - **Prop-driven & consistent**: prefer clear props over compound anatomy for core controls.
24
+ - **Controls**: comfortable `h-10` default; shared focus/disabled/error states.
25
+ - **Forms**: `label`, `description`, `error` (with `aria-invalid`/`aria-describedby`/`role="alert"` where relevant), `required` mark.
26
+ - **Icons**: default internal affordances use **Phosphor**. `startIcon`/`endIcon`, `Alert.icon` and `IconButton` children accept any icon component (`IconComponent { size?, className? }`), so user-facing icons are pack-agnostic. The internal set (`check`, `minus`, `chevron`, `close`, `spinner`) is replaceable via `IconProvider`; `Toaster` exposes its own `icons` prop.
27
+ - **Loading**: preserve layout (spinner + label), set `disabled`/`aria-busy`.
28
+
29
+ ## Structure & exports
30
+ - One folder per component: `src/components/ui/<name>/<name>.tsx`, `<name>-variants.ts`, `<name>.test.tsx`, `index.ts`.
31
+ - Extract `cva` into `<name>-variants.ts` **without** `"use client"`, so variants can be used in server components.
32
+ - The folder `index.ts` exports the component and its variants explicitly. The main `src/index.ts` barrel re-exports folders, and `package.json` exposes subpaths (`@nphuonha/ui-kit/<name>`) for smaller client bundles.
33
+ - Keep tests colocated with the component. Prefer a11y smoke tests plus token/scale regressions for components with meaningful state.
34
+
35
+ ## Stack notes
36
+ - Overlays/behavior: `@base-ui/react`. Toast: `sonner` (restyled via tokens).
37
+ - Prefer CSS transitions; avoid adding animation libraries for core MVP unless necessary.
38
+ - When adding components, follow the token contract + these conventions and add/extend colocated tests.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nphuonha
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,115 @@
1
- # Temporary Holding Version
1
+ # @nphuonha/ui-kit
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Solid, clean, flat, modern React UI kit. **Base UI** for behavior/a11y + owned tokens/styles + a consistent prop-driven API. Accent is vibrant by default and customizable by color generation.
4
+
5
+ > **v0.3.0** adds `BottomNav`, `DesktopNav` and `activeIcon` support for selected navigation items.
6
+
7
+ - **Docs:** https://ui.nphuonha.id.vn
8
+ - **Agent guide:** https://ui.nphuonha.id.vn/docs/agents
9
+ - **llms.txt:** https://ui.nphuonha.id.vn/llms.txt
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install @nphuonha/ui-kit
15
+ ```
16
+
17
+ Peer dependencies: React 19, React DOM 19, Tailwind CSS 4.
18
+
19
+ The published package ships compiled ESM + `.d.ts`, so it works with Next.js, Vite, Remix and other bundlers. No `transpilePackages` step is required.
20
+
21
+ ## Usage
22
+
23
+ ```tsx
24
+ import "@nphuonha/ui-kit/styles.css";
25
+ import { Button, Card, CardContent, Input, Toaster } from "@nphuonha/ui-kit";
26
+
27
+ export function App() {
28
+ return (
29
+ <div data-ui="kit">
30
+ <Toaster />
31
+ <Card>
32
+ <CardContent className="flex gap-2 pt-5">
33
+ <Input placeholder="Email" />
34
+ <Button>Continue</Button>
35
+ </CardContent>
36
+ </Card>
37
+ </div>
38
+ );
39
+ }
40
+ ```
41
+
42
+ - Scope tokens/overlays with `data-ui="kit"` (add to `<html>` for portalled overlays).
43
+ - Dark mode: add the `dark` class to the same element.
44
+ - Change the accent at runtime:
45
+
46
+ ```tsx
47
+ import { accentStyle } from "@nphuonha/ui-kit";
48
+
49
+ <div data-ui="kit" style={accentStyle("#0d9488", "light")}>
50
+ <App />
51
+ </div>
52
+ ```
53
+
54
+ Or generate variables directly:
55
+
56
+ ```ts
57
+ import { generateAccentVars } from "@nphuonha/ui-kit";
58
+
59
+ const vars = generateAccentVars("#0d9488", "dark");
60
+ ```
61
+
62
+ Tailwind v4: the package stylesheet imports Tailwind. If your setup ignores `@source` from dependencies, add:
63
+
64
+ ```css
65
+ @source "../node_modules/@nphuonha/ui-kit/dist/components";
66
+ ```
67
+
68
+ ## Granular imports
69
+
70
+ The main barrel is convenient. For smaller client bundles, import from a component subpath:
71
+
72
+ ```tsx
73
+ import { Button, buttonVariants } from "@nphuonha/ui-kit/button";
74
+ import { Field } from "@nphuonha/ui-kit/field";
75
+ import { Input } from "@nphuonha/ui-kit/input";
76
+ ```
77
+
78
+ Variants live in server-safe `*-variants.ts` files and can be used from server components.
79
+
80
+ ## Icons
81
+
82
+ The kit uses **Phosphor** for its internal affordances (check, minus, chevron, close, spinner). User-facing icons are pack-agnostic — pass any component that accepts `size` and `className`:
83
+
84
+ ```tsx
85
+ <Button startIcon={Plus}>New item</Button>
86
+ <IconButton aria-label="Search"><Search /></IconButton>
87
+ <Alert icon={Info} title="Heads up">...</Alert>
88
+ ```
89
+
90
+ Swap the internal set with `IconProvider`:
91
+
92
+ ```tsx
93
+ import { IconProvider } from "@nphuonha/ui-kit";
94
+ import { Check, ChevronUpDown, Loader, Minus, X } from "your-icon-pack";
95
+
96
+ <IconProvider
97
+ icons={{ check: Check, minus: Minus, chevron: ChevronUpDown, close: X, spinner: Loader }}
98
+ >
99
+ <App />
100
+ </IconProvider>
101
+ ```
102
+
103
+ `Toaster` also accepts an `icons` prop for `success`, `info`, `warning`, `error`, `loading` and `close`.
104
+
105
+ ## Components
106
+
107
+ `Button`, `IconButton`, `Input`, `Textarea`, `Field`/`Label`, `Checkbox`, `RadioGroup`, `Switch`, `Select`, `Dialog`, `Sheet`, `Popover`, `Tooltip`, `Tabs`, `BottomNav`, `DesktopNav`, `Card` (+Header/Title/Description/Content/Footer), `Badge`, `Alert`, `Separator`, `Spinner`, `Toaster`/`toast`.
108
+
109
+ `BottomNav` and `DesktopNav` items accept an optional `activeIcon` to swap to a filled or alternate icon when selected.
110
+
111
+ Live previews, code and API reference: https://ui.nphuonha.id.vn
112
+
113
+ ## License
114
+
115
+ MIT
@@ -0,0 +1,18 @@
1
+ import * as React from "react";
2
+ export type KitIconName = "check" | "minus" | "chevron" | "close" | "spinner";
3
+ export type KitIcon = React.ComponentType<{
4
+ size?: number;
5
+ } & React.SVGProps<SVGSVGElement>>;
6
+ export interface IconProviderProps {
7
+ icons?: Partial<Record<KitIconName, KitIcon>>;
8
+ children: React.ReactNode;
9
+ }
10
+ /**
11
+ * Default internal icons come from Phosphor. Swap any of them for another pack:
12
+ *
13
+ * <IconProvider icons={{ check: LucideCheck, close: LucideX }}>
14
+ * <App />
15
+ * </IconProvider>
16
+ */
17
+ export declare function IconProvider({ icons, children }: IconProviderProps): React.JSX.Element;
18
+ export declare function useKitIcon(name: KitIconName): KitIcon;
@@ -0,0 +1,26 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { CaretUpDownIcon, CheckIcon, MinusIcon, SpinnerIcon, XIcon, } from "@phosphor-icons/react";
5
+ const defaultIcons = {
6
+ check: (props) => _jsx(CheckIcon, { ...props, weight: "bold" }),
7
+ minus: (props) => _jsx(MinusIcon, { ...props, weight: "bold" }),
8
+ chevron: (props) => _jsx(CaretUpDownIcon, { ...props, weight: "bold" }),
9
+ close: (props) => _jsx(XIcon, { ...props, weight: "bold" }),
10
+ spinner: SpinnerIcon,
11
+ };
12
+ const IconContext = React.createContext({});
13
+ /**
14
+ * Default internal icons come from Phosphor. Swap any of them for another pack:
15
+ *
16
+ * <IconProvider icons={{ check: LucideCheck, close: LucideX }}>
17
+ * <App />
18
+ * </IconProvider>
19
+ */
20
+ export function IconProvider({ icons, children }) {
21
+ return _jsx(IconContext.Provider, { value: icons ?? {}, children: children });
22
+ }
23
+ export function useKitIcon(name) {
24
+ const icons = React.useContext(IconContext);
25
+ return icons[name] ?? defaultIcons[name];
26
+ }
@@ -0,0 +1,5 @@
1
+ import { type VariantProps } from "class-variance-authority";
2
+ export declare const alertVariants: (props?: ({
3
+ variant?: "neutral" | "info" | "success" | "warning" | "danger" | null | undefined;
4
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
5
+ export type AlertVariants = VariantProps<typeof alertVariants>;
@@ -0,0 +1,13 @@
1
+ import { cva } from "class-variance-authority";
2
+ export const alertVariants = cva("surface-curved-soft relative flex w-full items-start gap-3 rounded-card px-4 py-3.5 text-sm", {
3
+ variants: {
4
+ variant: {
5
+ neutral: "bg-surface text-foreground",
6
+ info: "bg-info/10 text-foreground",
7
+ success: "bg-success/10 text-foreground",
8
+ warning: "bg-warning/12 text-foreground",
9
+ danger: "bg-destructive/10 text-foreground",
10
+ },
11
+ },
12
+ defaultVariants: { variant: "neutral" },
13
+ });
@@ -0,0 +1,9 @@
1
+ import * as React from "react";
2
+ import type { VariantProps } from "class-variance-authority";
3
+ import { type IconComponent } from "../../../lib/utils.js";
4
+ import { alertVariants } from "./alert-variants.js";
5
+ export interface AlertProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof alertVariants> {
6
+ title?: string;
7
+ icon?: IconComponent;
8
+ }
9
+ export declare function Alert({ className, variant, title, icon: Icon, children, ...props }: AlertProps): React.JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../../../lib/utils.js";
3
+ import { alertVariants } from "./alert-variants.js";
4
+ const iconTone = {
5
+ neutral: "text-muted-foreground",
6
+ info: "text-info",
7
+ success: "text-success",
8
+ warning: "text-warning",
9
+ danger: "text-destructive",
10
+ };
11
+ export function Alert({ className, variant = "neutral", title, icon: Icon, children, ...props }) {
12
+ return (_jsxs("div", { role: "status", className: cn(alertVariants({ variant }), className), ...props, children: [Icon ? _jsx(Icon, { size: 18, className: cn("mt-0.5 shrink-0", iconTone[variant ?? "neutral"]) }) : null, _jsxs("div", { className: "min-w-0 flex-1", children: [title ? _jsx("div", { className: "font-semibold leading-5 tracking-[-0.01em]", children: title }) : null, children ? _jsx("div", { className: cn("text-muted-foreground", title && "mt-0.5"), children: children }) : null] })] }));
13
+ }
@@ -0,0 +1,2 @@
1
+ export { Alert, type AlertProps } from "./alert.js";
2
+ export { alertVariants, type AlertVariants } from "./alert-variants.js";
@@ -0,0 +1,2 @@
1
+ export { Alert } from "./alert.js";
2
+ export { alertVariants } from "./alert-variants.js";
@@ -0,0 +1,5 @@
1
+ import { type VariantProps } from "class-variance-authority";
2
+ export declare const badgeVariants: (props?: ({
3
+ variant?: "neutral" | "info" | "success" | "warning" | "danger" | "primary" | "solid" | "outline" | null | undefined;
4
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
5
+ export type BadgeVariants = VariantProps<typeof badgeVariants>;
@@ -0,0 +1,16 @@
1
+ import { cva } from "class-variance-authority";
2
+ export const badgeVariants = cva("surface-curved-soft relative inline-flex items-center gap-1 whitespace-nowrap rounded-pill px-2.5 py-0.5 text-xs font-semibold tracking-[-0.01em] transition-colors", {
3
+ variants: {
4
+ variant: {
5
+ neutral: "bg-surface text-foreground",
6
+ primary: "bg-primary-soft text-primary-soft-foreground",
7
+ success: "bg-success/12 text-success",
8
+ warning: "bg-warning/14 text-warning",
9
+ danger: "bg-destructive/12 text-destructive",
10
+ info: "bg-info/12 text-info",
11
+ solid: "bg-primary text-primary-foreground",
12
+ outline: "bg-transparent text-foreground shadow-[inset_0_0_0_1px_var(--border)]",
13
+ },
14
+ },
15
+ defaultVariants: { variant: "neutral" },
16
+ });
@@ -0,0 +1,6 @@
1
+ import * as React from "react";
2
+ import type { VariantProps } from "class-variance-authority";
3
+ import { badgeVariants } from "./badge-variants.js";
4
+ export interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement>, VariantProps<typeof badgeVariants> {
5
+ }
6
+ export declare function Badge({ className, variant, ...props }: BadgeProps): React.JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../../../lib/utils.js";
3
+ import { badgeVariants } from "./badge-variants.js";
4
+ export function Badge({ className, variant, ...props }) {
5
+ return _jsx("span", { className: cn(badgeVariants({ variant }), className), ...props });
6
+ }
@@ -0,0 +1,2 @@
1
+ export { Badge, type BadgeProps } from "./badge.js";
2
+ export { badgeVariants, type BadgeVariants } from "./badge-variants.js";
@@ -0,0 +1,2 @@
1
+ export { Badge } from "./badge.js";
2
+ export { badgeVariants } from "./badge-variants.js";
@@ -0,0 +1,20 @@
1
+ import * as React from "react";
2
+ import { type IconComponent } from "../../../lib/utils.js";
3
+ export interface BottomNavItem {
4
+ value: string;
5
+ label: React.ReactNode;
6
+ icon: IconComponent;
7
+ activeIcon?: IconComponent;
8
+ href?: string;
9
+ badge?: React.ReactNode;
10
+ disabled?: boolean;
11
+ }
12
+ export interface BottomNavProps extends Omit<React.ComponentPropsWithoutRef<"nav">, "onChange"> {
13
+ items: BottomNavItem[];
14
+ value?: string;
15
+ defaultValue?: string;
16
+ onValueChange?: (value: string) => void;
17
+ /** Pin the bar to the bottom of the viewport. */
18
+ fixed?: boolean;
19
+ }
20
+ export declare const BottomNav: React.ForwardRefExoticComponent<BottomNavProps & React.RefAttributes<HTMLElement>>;
@@ -0,0 +1,36 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../../../lib/utils.js";
5
+ export const BottomNav = React.forwardRef(({ items, value, defaultValue, onValueChange, fixed = false, className, "aria-label": ariaLabel = "Bottom navigation", ...props }, ref) => {
6
+ const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue);
7
+ const activeValue = value ?? uncontrolledValue;
8
+ const select = (next) => {
9
+ if (value === undefined)
10
+ setUncontrolledValue(next);
11
+ onValueChange?.(next);
12
+ };
13
+ return (_jsx("nav", { ref: ref, "aria-label": ariaLabel, className: cn("surface-curved relative rounded-[24px] bg-card p-1.5 shadow-[var(--shadow-raised)]", fixed
14
+ ? "fixed inset-x-3 bottom-[calc(env(safe-area-inset-bottom)+0.75rem)] z-40 mx-auto max-w-md"
15
+ : "w-full", className), ...props, children: _jsx("div", { className: "flex items-stretch gap-1", children: items.map((item) => {
16
+ const active = item.value === activeValue;
17
+ const Icon = (active && item.activeIcon) || item.icon;
18
+ const content = (_jsxs(_Fragment, { children: [_jsxs("span", { className: cn("relative flex h-7 min-w-12 items-center justify-center rounded-pill px-2.5 transition-[background-color,color,box-shadow] duration-[var(--duration-ui)] ease-[var(--ease-out-ui)]", active
19
+ ? "bg-primary text-primary-foreground shadow-[var(--shadow-raised)]"
20
+ : "text-muted-foreground group-hover:bg-surface group-hover:text-foreground"), children: [_jsx(Icon, { size: 18 }), item.badge != null ? (_jsx("span", { className: cn("absolute -right-1 -top-1 inline-flex min-w-4 items-center justify-center rounded-pill px-1 text-[9px] font-semibold leading-4", active
21
+ ? "bg-card text-foreground shadow-sm"
22
+ : "bg-primary text-primary-foreground"), children: item.badge })) : null] }), _jsx("span", { className: cn("max-w-full truncate text-[10px] font-medium leading-none tracking-[-0.01em]", active ? "font-semibold text-foreground" : "text-muted-foreground"), children: item.label })] }));
23
+ const itemClassName = cn("group relative flex min-w-0 flex-1 flex-col items-center justify-center gap-1 rounded-[18px] px-1 py-1.5 outline-none transition-colors duration-[var(--duration-ui)]", "focus-visible:ring-2 focus-visible:ring-ring/40", item.disabled && "pointer-events-none opacity-45");
24
+ if (item.href) {
25
+ return (_jsx("a", { href: item.href, "aria-current": active ? "page" : undefined, "aria-disabled": item.disabled || undefined, tabIndex: item.disabled ? -1 : undefined, className: itemClassName, onClick: (event) => {
26
+ if (item.disabled) {
27
+ event.preventDefault();
28
+ return;
29
+ }
30
+ select(item.value);
31
+ }, children: content }, item.value));
32
+ }
33
+ return (_jsx("button", { type: "button", disabled: item.disabled, "aria-current": active ? "page" : undefined, className: itemClassName, onClick: () => select(item.value), children: content }, item.value));
34
+ }) }) }));
35
+ });
36
+ BottomNav.displayName = "BottomNav";
@@ -0,0 +1 @@
1
+ export { BottomNav, type BottomNavItem, type BottomNavProps } from "./bottom-nav.js";
@@ -0,0 +1 @@
1
+ export { BottomNav } from "./bottom-nav.js";
@@ -0,0 +1,7 @@
1
+ import { type VariantProps } from "class-variance-authority";
2
+ export declare const buttonVariants: (props?: ({
3
+ variant?: "danger" | "primary" | "outline" | "secondary" | "ghost" | "soft" | null | undefined;
4
+ size?: "icon" | "sm" | "default" | "lg" | "icon-sm" | null | undefined;
5
+ shape?: "rounded" | "pill" | null | undefined;
6
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
+ export type ButtonVariants = VariantProps<typeof buttonVariants>;
@@ -0,0 +1,29 @@
1
+ import { cva } from "class-variance-authority";
2
+ export const buttonVariants = cva("relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap text-sm font-semibold tracking-[-0.01em] transition-[background-color,color,box-shadow,transform] duration-[var(--duration-ui)] ease-[var(--ease-out-ui)] outline-none select-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-45 data-[loading]:opacity-80 motion-safe:active:scale-[0.97]", {
3
+ variants: {
4
+ variant: {
5
+ primary: "surface-curved bg-primary text-primary-foreground shadow-[var(--shadow-raised)] hover:bg-primary-hover active:bg-primary-active",
6
+ secondary: "surface-curved-soft bg-field text-field-foreground hover:bg-field-hover",
7
+ outline: "bg-transparent text-foreground shadow-[inset_0_0_0_1.5px_var(--border)] hover:bg-surface",
8
+ ghost: "bg-transparent text-foreground hover:bg-surface active:bg-surface-hover",
9
+ danger: "surface-curved bg-destructive text-destructive-foreground shadow-[var(--shadow-raised)] hover:bg-destructive/90",
10
+ soft: "surface-curved-soft bg-primary-soft text-primary-soft-foreground hover:bg-primary-soft/75",
11
+ },
12
+ size: {
13
+ sm: "h-9 px-3.5 text-[13px]",
14
+ default: "h-10 px-4",
15
+ lg: "h-11 px-5 text-[15px]",
16
+ icon: "size-10 p-0",
17
+ "icon-sm": "size-9 p-0",
18
+ },
19
+ shape: {
20
+ rounded: "rounded-control",
21
+ pill: "rounded-pill",
22
+ },
23
+ },
24
+ defaultVariants: {
25
+ variant: "primary",
26
+ size: "default",
27
+ shape: "rounded",
28
+ },
29
+ });
@@ -0,0 +1,10 @@
1
+ import * as React from "react";
2
+ import { Button as ButtonPrimitive } from "@base-ui/react/button";
3
+ import { type ButtonVariants } from "./button-variants.js";
4
+ import { type IconComponent } from "../../../lib/utils.js";
5
+ export interface ButtonProps extends ButtonPrimitive.Props, ButtonVariants {
6
+ loading?: boolean;
7
+ startIcon?: IconComponent;
8
+ endIcon?: IconComponent;
9
+ }
10
+ export declare const Button: React.ForwardRefExoticComponent<Omit<ButtonProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Button as ButtonPrimitive } from "@base-ui/react/button";
5
+ import { Spinner } from "../spinner/index.js";
6
+ import { buttonVariants } from "./button-variants.js";
7
+ import { cn } from "../../../lib/utils.js";
8
+ export const Button = React.forwardRef(({ className, variant, size, shape, loading, startIcon: StartIcon, endIcon: EndIcon, children, disabled, ...props }, ref) => {
9
+ const iconOnly = size === "icon" || size === "icon-sm";
10
+ return (_jsxs(ButtonPrimitive, { ref: ref, disabled: loading || disabled, "aria-busy": loading || undefined, "data-loading": loading || undefined, className: cn(buttonVariants({ variant, size, shape }), className), ...props, children: [loading ? _jsx(Spinner, { size: 16, className: "shrink-0" }) : null, !loading && StartIcon ? _jsx(StartIcon, { size: 16, className: "shrink-0" }) : null, children != null && children !== "" && !(loading && iconOnly) ? (iconOnly ? (_jsx("span", { "aria-hidden": "true", className: "inline-flex items-center justify-center", children: children })) : (_jsx("span", { className: "truncate", children: children }))) : null, !loading && EndIcon ? _jsx(EndIcon, { size: 16, className: "shrink-0" }) : null] }));
11
+ });
12
+ Button.displayName = "Button";
@@ -0,0 +1,2 @@
1
+ export { Button, type ButtonProps } from "./button.js";
2
+ export { buttonVariants, type ButtonVariants } from "./button-variants.js";
@@ -0,0 +1,2 @@
1
+ export { Button } from "./button.js";
2
+ export { buttonVariants } from "./button-variants.js";
@@ -0,0 +1,7 @@
1
+ import * as React from "react";
2
+ export declare function Card({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element;
3
+ export declare function CardHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element;
4
+ export declare function CardTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>): React.JSX.Element;
5
+ export declare function CardDescription({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>): React.JSX.Element;
6
+ export declare function CardContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element;
7
+ export declare function CardFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element;
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../../../lib/utils.js";
3
+ export function Card({ className, ...props }) {
4
+ return (_jsx("div", { className: cn("surface-curved relative rounded-card bg-card text-card-foreground shadow-[var(--shadow-soft)]", className), ...props }));
5
+ }
6
+ export function CardHeader({ className, ...props }) {
7
+ return _jsx("div", { className: cn("flex flex-col gap-1.5 p-6 pb-0", className), ...props });
8
+ }
9
+ export function CardTitle({ className, ...props }) {
10
+ return _jsx("h3", { className: cn("text-base font-semibold tracking-[-0.015em] leading-tight", className), ...props });
11
+ }
12
+ export function CardDescription({ className, ...props }) {
13
+ return _jsx("p", { className: cn("text-sm leading-relaxed text-muted-foreground", className), ...props });
14
+ }
15
+ export function CardContent({ className, ...props }) {
16
+ return _jsx("div", { className: cn("p-6", className), ...props });
17
+ }
18
+ export function CardFooter({ className, ...props }) {
19
+ return _jsx("div", { className: cn("flex items-center gap-2 p-6 pt-0", className), ...props });
20
+ }
@@ -0,0 +1 @@
1
+ export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "./card.js";
@@ -0,0 +1 @@
1
+ export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "./card.js";
@@ -0,0 +1,8 @@
1
+ import * as React from "react";
2
+ import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox";
3
+ export interface CheckboxProps extends Omit<React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>, "children"> {
4
+ label?: React.ReactNode;
5
+ description?: React.ReactNode;
6
+ error?: boolean;
7
+ }
8
+ export declare const Checkbox: React.ForwardRefExoticComponent<CheckboxProps & React.RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,18 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox";
5
+ import { cn } from "../../../lib/utils.js";
6
+ import { useKitIcon } from "../_shared/icon-provider.js";
7
+ export const Checkbox = React.forwardRef(({ className, label, description, error, id, ...props }, ref) => {
8
+ const CheckIcon = useKitIcon("check");
9
+ const MinusIcon = useKitIcon("minus");
10
+ const autoId = React.useId();
11
+ const controlId = id ?? autoId;
12
+ const descriptionId = description ? `${controlId}-description` : undefined;
13
+ const control = (_jsx(CheckboxPrimitive.Root, { ref: ref, id: controlId, "aria-invalid": error || undefined, "aria-describedby": descriptionId, className: cn("surface-inset peer inline-flex size-5 shrink-0 items-center justify-center rounded-[7px] bg-field text-primary-foreground transition-[background-color,box-shadow] duration-[var(--duration-ui)] ease-[var(--ease-out-ui)] outline-none focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:ring-offset-2 focus-visible:ring-offset-background data-[checked]:bg-primary data-[indeterminate]:bg-primary disabled:cursor-not-allowed disabled:opacity-45", error && "bg-destructive/15 data-[checked]:bg-destructive", className), ...props, children: _jsxs(CheckboxPrimitive.Indicator, { className: "flex items-center justify-center text-current data-[unchecked]:hidden", children: [_jsx(CheckIcon, { size: 13, className: "in-data-[indeterminate]:hidden" }), _jsx(MinusIcon, { size: 13, className: "hidden in-data-[indeterminate]:block" })] }) }));
14
+ if (!label && !description)
15
+ return control;
16
+ return (_jsxs("div", { className: "flex items-start gap-2.5", children: [control, _jsxs("div", { className: "grid gap-0.5 pt-0.5", children: [label ? (_jsx("label", { htmlFor: controlId, className: "cursor-pointer text-sm font-medium leading-none text-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-45", children: label })) : null, description ? _jsx("p", { id: descriptionId, className: "text-xs text-muted-foreground", children: description }) : null] })] }));
17
+ });
18
+ Checkbox.displayName = "Checkbox";
@@ -0,0 +1 @@
1
+ export { Checkbox, type CheckboxProps } from "./checkbox.js";
@@ -0,0 +1 @@
1
+ export { Checkbox } from "./checkbox.js";
@@ -0,0 +1,18 @@
1
+ import * as React from "react";
2
+ import { type IconComponent } from "../../../lib/utils.js";
3
+ export interface DesktopNavItem {
4
+ value: string;
5
+ label: React.ReactNode;
6
+ icon?: IconComponent;
7
+ activeIcon?: IconComponent;
8
+ href?: string;
9
+ badge?: React.ReactNode;
10
+ disabled?: boolean;
11
+ }
12
+ export interface DesktopNavProps extends Omit<React.ComponentPropsWithoutRef<"nav">, "onChange"> {
13
+ items: DesktopNavItem[];
14
+ value?: string;
15
+ defaultValue?: string;
16
+ onValueChange?: (value: string) => void;
17
+ }
18
+ export declare const DesktopNav: React.ForwardRefExoticComponent<DesktopNavProps & React.RefAttributes<HTMLElement>>;
@@ -0,0 +1,37 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../../../lib/utils.js";
5
+ export const DesktopNav = React.forwardRef(({ items, value, defaultValue, onValueChange, className, "aria-label": ariaLabel = "Desktop navigation", ...props }, ref) => {
6
+ const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue);
7
+ const activeValue = value ?? uncontrolledValue;
8
+ const select = (next) => {
9
+ if (value === undefined)
10
+ setUncontrolledValue(next);
11
+ onValueChange?.(next);
12
+ };
13
+ return (_jsx("nav", { ref: ref, "aria-label": ariaLabel, className: cn(
14
+ // Keep `relative`: surface-curved-soft paints an absolute ::before rim,
15
+ // which otherwise anchors to the nearest positioned ancestor.
16
+ "surface-curved-soft relative scrollbar-none inline-flex max-w-full items-center gap-0.5 overflow-x-auto rounded-pill bg-surface p-1", className), ...props, children: items.map((item) => {
17
+ const active = item.value === activeValue;
18
+ const Icon = (active && item.activeIcon) || item.icon;
19
+ const content = (_jsxs(_Fragment, { children: [Icon ? _jsx(Icon, { size: 15, className: "shrink-0" }) : null, _jsx("span", { className: "whitespace-nowrap", children: item.label }), item.badge != null ? (_jsx("span", { className: cn("ml-0.5 inline-flex min-w-4 items-center justify-center rounded-pill px-1 text-[10px] font-semibold leading-4", active
20
+ ? "bg-primary-soft text-primary-soft-foreground"
21
+ : "bg-surface-hover text-muted-foreground"), children: item.badge })) : null] }));
22
+ const itemClassName = cn("relative inline-flex h-9 shrink-0 items-center justify-center gap-2 rounded-pill px-3.5 text-sm font-medium outline-none transition-[background-color,color,box-shadow] duration-[var(--duration-ui)]", "focus-visible:ring-2 focus-visible:ring-ring/40", active
23
+ ? "surface-curved bg-card text-foreground shadow-sm"
24
+ : "text-muted-foreground hover:bg-card/60 hover:text-foreground", item.disabled && "pointer-events-none opacity-45");
25
+ if (item.href) {
26
+ return (_jsx("a", { href: item.href, "aria-current": active ? "page" : undefined, "aria-disabled": item.disabled || undefined, tabIndex: item.disabled ? -1 : undefined, className: itemClassName, onClick: (event) => {
27
+ if (item.disabled) {
28
+ event.preventDefault();
29
+ return;
30
+ }
31
+ select(item.value);
32
+ }, children: content }, item.value));
33
+ }
34
+ return (_jsx("button", { type: "button", disabled: item.disabled, "aria-current": active ? "page" : undefined, className: itemClassName, onClick: () => select(item.value), children: content }, item.value));
35
+ }) }));
36
+ });
37
+ DesktopNav.displayName = "DesktopNav";
@@ -0,0 +1 @@
1
+ export { DesktopNav, type DesktopNavItem, type DesktopNavProps } from "./desktop-nav.js";
@@ -0,0 +1 @@
1
+ export { DesktopNav } from "./desktop-nav.js";