@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.
- package/CONVENTIONS.md +38 -0
- package/LICENSE +21 -0
- package/README.md +114 -2
- package/dist/components/ui/_shared/icon-provider.d.ts +18 -0
- package/dist/components/ui/_shared/icon-provider.js +26 -0
- package/dist/components/ui/alert/alert-variants.d.ts +5 -0
- package/dist/components/ui/alert/alert-variants.js +13 -0
- package/dist/components/ui/alert/alert.d.ts +9 -0
- package/dist/components/ui/alert/alert.js +13 -0
- package/dist/components/ui/alert/index.d.ts +2 -0
- package/dist/components/ui/alert/index.js +2 -0
- package/dist/components/ui/badge/badge-variants.d.ts +5 -0
- package/dist/components/ui/badge/badge-variants.js +16 -0
- package/dist/components/ui/badge/badge.d.ts +6 -0
- package/dist/components/ui/badge/badge.js +6 -0
- package/dist/components/ui/badge/index.d.ts +2 -0
- package/dist/components/ui/badge/index.js +2 -0
- package/dist/components/ui/bottom-nav/bottom-nav.d.ts +20 -0
- package/dist/components/ui/bottom-nav/bottom-nav.js +36 -0
- package/dist/components/ui/bottom-nav/index.d.ts +1 -0
- package/dist/components/ui/bottom-nav/index.js +1 -0
- package/dist/components/ui/button/button-variants.d.ts +7 -0
- package/dist/components/ui/button/button-variants.js +29 -0
- package/dist/components/ui/button/button.d.ts +10 -0
- package/dist/components/ui/button/button.js +12 -0
- package/dist/components/ui/button/index.d.ts +2 -0
- package/dist/components/ui/button/index.js +2 -0
- package/dist/components/ui/card/card.d.ts +7 -0
- package/dist/components/ui/card/card.js +20 -0
- package/dist/components/ui/card/index.d.ts +1 -0
- package/dist/components/ui/card/index.js +1 -0
- package/dist/components/ui/checkbox/checkbox.d.ts +8 -0
- package/dist/components/ui/checkbox/checkbox.js +18 -0
- package/dist/components/ui/checkbox/index.d.ts +1 -0
- package/dist/components/ui/checkbox/index.js +1 -0
- package/dist/components/ui/desktop-nav/desktop-nav.d.ts +18 -0
- package/dist/components/ui/desktop-nav/desktop-nav.js +37 -0
- package/dist/components/ui/desktop-nav/index.d.ts +1 -0
- package/dist/components/ui/desktop-nav/index.js +1 -0
- package/dist/components/ui/dialog/dialog.d.ts +15 -0
- package/dist/components/ui/dialog/dialog.js +10 -0
- package/dist/components/ui/dialog/index.d.ts +1 -0
- package/dist/components/ui/dialog/index.js +1 -0
- package/dist/components/ui/field/field.d.ts +11 -0
- package/dist/components/ui/field/field.js +22 -0
- package/dist/components/ui/field/index.d.ts +1 -0
- package/dist/components/ui/field/index.js +1 -0
- package/dist/components/ui/icon-button/icon-button.d.ts +7 -0
- package/dist/components/ui/icon-button/icon-button.js +7 -0
- package/dist/components/ui/icon-button/index.d.ts +1 -0
- package/dist/components/ui/icon-button/index.js +1 -0
- package/dist/components/ui/input/index.d.ts +1 -0
- package/dist/components/ui/input/index.js +1 -0
- package/dist/components/ui/input/input.d.ts +5 -0
- package/dist/components/ui/input/input.js +6 -0
- package/dist/components/ui/label/index.d.ts +1 -0
- package/dist/components/ui/label/index.js +1 -0
- package/dist/components/ui/label/label.d.ts +5 -0
- package/dist/components/ui/label/label.js +6 -0
- package/dist/components/ui/popover/index.d.ts +1 -0
- package/dist/components/ui/popover/index.js +1 -0
- package/dist/components/ui/popover/popover.d.ts +14 -0
- package/dist/components/ui/popover/popover.js +7 -0
- package/dist/components/ui/radio-group/index.d.ts +1 -0
- package/dist/components/ui/radio-group/index.js +1 -0
- package/dist/components/ui/radio-group/radio-group.d.ts +17 -0
- package/dist/components/ui/radio-group/radio-group.js +17 -0
- package/dist/components/ui/select/index.d.ts +1 -0
- package/dist/components/ui/select/index.js +1 -0
- package/dist/components/ui/select/select.d.ts +21 -0
- package/dist/components/ui/select/select.js +10 -0
- package/dist/components/ui/separator/index.d.ts +1 -0
- package/dist/components/ui/separator/index.js +1 -0
- package/dist/components/ui/separator/separator.d.ts +6 -0
- package/dist/components/ui/separator/separator.js +5 -0
- package/dist/components/ui/sheet/index.d.ts +1 -0
- package/dist/components/ui/sheet/index.js +1 -0
- package/dist/components/ui/sheet/sheet.d.ts +15 -0
- package/dist/components/ui/sheet/sheet.js +15 -0
- package/dist/components/ui/spinner/index.d.ts +1 -0
- package/dist/components/ui/spinner/index.js +1 -0
- package/dist/components/ui/spinner/spinner.d.ts +5 -0
- package/dist/components/ui/spinner/spinner.js +8 -0
- package/dist/components/ui/switch/index.d.ts +1 -0
- package/dist/components/ui/switch/index.js +1 -0
- package/dist/components/ui/switch/switch.d.ts +7 -0
- package/dist/components/ui/switch/switch.js +15 -0
- package/dist/components/ui/tabs/index.d.ts +1 -0
- package/dist/components/ui/tabs/index.js +1 -0
- package/dist/components/ui/tabs/tabs.d.ts +14 -0
- package/dist/components/ui/tabs/tabs.js +7 -0
- package/dist/components/ui/textarea/index.d.ts +1 -0
- package/dist/components/ui/textarea/index.js +1 -0
- package/dist/components/ui/textarea/textarea.d.ts +5 -0
- package/dist/components/ui/textarea/textarea.js +6 -0
- package/dist/components/ui/toast/index.d.ts +1 -0
- package/dist/components/ui/toast/index.js +1 -0
- package/dist/components/ui/toast/toast.d.ts +19 -0
- package/dist/components/ui/toast/toast.js +20 -0
- package/dist/components/ui/tooltip/index.d.ts +1 -0
- package/dist/components/ui/tooltip/index.js +1 -0
- package/dist/components/ui/tooltip/tooltip.d.ts +12 -0
- package/dist/components/ui/tooltip/tooltip.js +9 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +31 -0
- package/dist/lib/color.d.ts +16 -0
- package/dist/lib/color.js +118 -0
- package/dist/lib/utils.d.ts +7 -0
- package/dist/lib/utils.js +5 -0
- package/dist/styles/base.css +212 -0
- package/dist/styles/theme.css +121 -0
- package/dist/styles.css +5 -0
- 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
|
-
#
|
|
1
|
+
# @nphuonha/ui-kit
|
|
2
2
|
|
|
3
|
-
|
|
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,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,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,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";
|