@preset.nz/ux-kit 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +90 -2
- package/package.json +51 -3
- package/src/components/avatar.tsx +35 -0
- package/src/components/badge.tsx +42 -0
- package/src/components/breadcrumb.tsx +102 -0
- package/src/components/button.tsx +56 -0
- package/src/components/card.tsx +92 -0
- package/src/components/checkbox.tsx +27 -0
- package/src/components/color-field.tsx +248 -0
- package/src/components/color-swatch.tsx +52 -0
- package/src/components/context-menu.tsx +249 -0
- package/src/components/dialog.tsx +137 -0
- package/src/components/dropdown-menu.tsx +249 -0
- package/src/components/empty-state.tsx +32 -0
- package/src/components/input.tsx +20 -0
- package/src/components/label.tsx +18 -0
- package/src/components/navigation-menu.tsx +144 -0
- package/src/components/number-field.tsx +428 -0
- package/src/components/popover.tsx +82 -0
- package/src/components/select.tsx +201 -0
- package/src/components/separator.tsx +25 -0
- package/src/components/sheet.tsx +124 -0
- package/src/components/side-panel.tsx +166 -0
- package/src/components/sidebar.tsx +735 -0
- package/src/components/skeleton.tsx +14 -0
- package/src/components/slider.tsx +75 -0
- package/src/components/snackbar.tsx +141 -0
- package/src/components/status-bar.tsx +38 -0
- package/src/components/tabs.tsx +78 -0
- package/src/components/textarea.tsx +18 -0
- package/src/components/toggle-group.tsx +86 -0
- package/src/components/toggle.tsx +43 -0
- package/src/components/toolbar.tsx +307 -0
- package/src/components/tooltip.tsx +54 -0
- package/src/graphics/adsr.ts +70 -0
- package/src/graphics/curve-editor.tsx +525 -0
- package/src/graphics/curve-edits.ts +129 -0
- package/src/graphics/index.ts +16 -0
- package/src/graphics/scale.ts +22 -0
- package/src/graphics/use-canvas.ts +56 -0
- package/src/graphics/use-tokens.ts +32 -0
- package/src/icons.ts +3 -0
- package/src/index.css +199 -0
- package/src/index.ts +37 -0
- package/src/lib/use-mobile.ts +18 -0
- package/src/lib/utils.ts +6 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 preset.nz
|
|
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,91 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @preset.nz/ux-kit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
One look and one way of working for the preset.nz desktop apps: tokens, palettes, fonts and Base UI primitives, in the studio's colours.
|
|
4
|
+
|
|
5
|
+
Pre-1.0: a minor version can break things, a patch never does.
|
|
6
|
+
|
|
7
|
+
## What is in it
|
|
8
|
+
|
|
9
|
+
- `src/index.css`: the colour variables (light and dark), the type scale and the font imports.
|
|
10
|
+
- `src/components/`: one module per primitive, on Base UI where Base UI has the part, plain React and Tailwind where it does not. See the table below.
|
|
11
|
+
- `src/icons.ts`: Phosphor, the kit's icon set.
|
|
12
|
+
- `playground/`: a Tauri and React app that shows the kit. It is the only place that touches rhizome; the kit does not depend on it.
|
|
13
|
+
|
|
14
|
+
Like [facets](https://github.com/preset-nz/facets), the kit ships unbuilt TypeScript. The consumer's Vite compiles it.
|
|
15
|
+
|
|
16
|
+
## Components
|
|
17
|
+
|
|
18
|
+
Union of the `src/components/ui` folders in Strata, Oblique, Shard and Map & Territory (Fault has none). Where several apps had the same component, the richest variant was taken. All are exported from the package index.
|
|
19
|
+
|
|
20
|
+
| Component | In kit | Came from | Notes |
|
|
21
|
+
|---|---|---|---|
|
|
22
|
+
| button | yes | Strata, Shard, M&T | Base UI Button |
|
|
23
|
+
| checkbox | yes | Strata, Oblique, Shard | |
|
|
24
|
+
| input | yes | all four | |
|
|
25
|
+
| label | yes | Strata, Oblique, Shard | |
|
|
26
|
+
| select | yes | Strata, Oblique, Shard | |
|
|
27
|
+
| separator | yes | all four | |
|
|
28
|
+
| dialog | yes | Oblique, Shard, M&T | Shard's themed variant. `render={<Button />}` replaces `asChild` |
|
|
29
|
+
| sheet | yes | M&T | Base UI Dialog docked to an edge (`side`) |
|
|
30
|
+
| popover | yes | Oblique | `PopoverAnchor` dropped: Base UI anchors through the Positioner's `anchor` prop |
|
|
31
|
+
| tooltip | yes | Oblique, M&T | `TooltipProvider delay` replaces `delayDuration` |
|
|
32
|
+
| tabs | yes | Oblique | `TabsTrigger` / `TabsContent` wrap Base UI `Tab` / `Panel`. Orientation uses `data-[orientation=...]` |
|
|
33
|
+
| toggle | yes | Oblique | `data-pressed` replaces `data-[state=on]` |
|
|
34
|
+
| toggle-group | yes | Oblique | Base UI `value` is always an array and `multiple` replaces `type="single" \| "multiple"` |
|
|
35
|
+
| slider | yes | Oblique, Shard, M&T | Takes a number or an array; one thumb per value |
|
|
36
|
+
| dropdown-menu | yes | Shard, M&T | M&T's fuller variant. `DropdownMenuLabel` is Base UI's `GroupLabel`, so it must sit inside a `DropdownMenuGroup` |
|
|
37
|
+
| context-menu | yes | Shard | Same shape as dropdown-menu, same `Label` caveat |
|
|
38
|
+
| navigation-menu | yes | M&T | Always the shared-viewport form. Radix's `NavigationMenuIndicator` and the `viewport={false}` mode are gone |
|
|
39
|
+
| snackbar | yes | Strata, Oblique | The kit's one toast, on Base UI Toast. `useSnackbar().show({ message, kind, description, action, persistent, timeout })` returns an id and `dismiss(id?)` closes one or all; kinds are `plain`, `success`, `warning`, `error`. Mount `SnackbarProvider` once and a `SnackbarViewport` inside it. `notify(opts)` and `dismissNotification(id?)` do the same from outside React |
|
|
40
|
+
| avatar | yes | M&T | Base UI Avatar |
|
|
41
|
+
| badge | yes | M&T | Plain, `render` prop replaces `asChild` |
|
|
42
|
+
| breadcrumb | yes | M&T | Plain `<a>`, no `next/link`. Use `render` for a router link |
|
|
43
|
+
| card | yes | M&T | Plain |
|
|
44
|
+
| skeleton | yes | M&T | Plain |
|
|
45
|
+
| textarea | yes | M&T | Plain, restyled to match Input |
|
|
46
|
+
| sidebar | yes | M&T | Plain, 700 lines. `asChild` became `render`. `useIsMobile` moved into the kit. Untested beyond the `collapsible="none"` form in the playground |
|
|
47
|
+
| toolbar | yes | Oblique, M&T | Base UI Toolbar (roving focus). `ToolbarItems` takes `groups` of `ToolbarItemSpec` (id, label, icon, shortcut, enabled, disabledReason, pressed, menu) and `onCommand(id)`; Oblique's `ToolButtonSpec` table with M&T's disabled reason. Disabled items stay focusable so the tooltip can say why. `ToolbarButton`, `ToolbarGroup`, `ToolbarSeparator`, `ToolbarSpacer` compose by hand. Shard's header buttons are inline JSX and Fault's are not a toolbar, so neither contributed |
|
|
48
|
+
| color-field | yes | M&T, Oblique, Strata | Swatch, hex text (commits on blur or Enter, bad input reverts), optional alpha (`#rrggbbaa`), preset grid in a Popover, native picker with `onPickStart`/`onPickEnd` for undo transactions (Oblique), `clearable` and null value (Strata, Oblique's optional colour), read-only, `trailing` slot for an eyedropper |
|
|
49
|
+
| number-field | yes | Base UI, Oblique | Base UI NumberField with the label as the ScrubArea (drag it; click it to type). Shift is coarse (x10), Alt/Option fine (x0.1), as Oblique's InputNumber, and `integer` keeps Alt on whole steps (integerStepFix). `onValueChange(value, { reason })` is `scrub`, `step` or `typed` (typed commits on blur or Enter, never per keystroke; Escape reverts). `onScrubStart`/`onScrubEnd` bracket one drag as the undo transaction, `onScrubCancel` is Escape mid-drag. `VectorField` is N of them with X/Y/Z/W labels. Scrubbing only on the label: no gutter scrub |
|
|
50
|
+
| color-swatch | yes | Strata, M&T, Oblique | Chip over a checkerboard; `null` is the dashed empty chip |
|
|
51
|
+
| side-panel | yes | Strata, M&T | Docked panel, props only (`open`, `width` owned by the app). Collapsed it leaves an icon rail that reopens it; the inner edge resizes by pointer drag (pointer capture) or arrow keys, clamped by `clampWidth`. `SidePanelHeader`, `SidePanelContent`. Strata's `SidePanel` ideas; persistence stays app-side |
|
|
52
|
+
| status-bar | yes | Oblique, Strata, M&T | `StatusBar`, `StatusItem` (optional label), `StatusSpacer`. Contents stay app-side |
|
|
53
|
+
| empty-state | yes | new | Icon, title, description, action slot |
|
|
54
|
+
|
|
55
|
+
M&T's `src/palettes/` (presets and derivation) was considered for the colour field and left out: it is map domain data and stays in the app, which passes its colours in through `presets`.
|
|
56
|
+
|
|
57
|
+
## Using it
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
pnpm add @preset.nz/ux-kit
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Then, in the app's CSS:
|
|
64
|
+
|
|
65
|
+
```css
|
|
66
|
+
@import "tailwindcss";
|
|
67
|
+
@source "../node_modules/@preset.nz/ux-kit/src";
|
|
68
|
+
@import "@preset.nz/ux-kit/index.css";
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
// vite.config.ts
|
|
73
|
+
resolve: { dedupe: ["react", "react-dom", "@tauri-apps/api", "@preset.nz/ux-kit"] }
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Without the `@source` line the build passes and the components render unstyled, because Tailwind does not scan `node_modules`. React 19 is a peer.
|
|
77
|
+
|
|
78
|
+
[facets](https://github.com/preset-nz/facets) 0.2 draws its number, slider and colour fields with the host app's components. An app on the kit re-exports them, for example `export { NumberField } from "@preset.nz/ux-kit"` in `src/components/ui/number-field.tsx`.
|
|
79
|
+
|
|
80
|
+
## Working on it
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
just install # pnpm install
|
|
84
|
+
just dev # the playground as a desktop app
|
|
85
|
+
just check # typecheck and lint the kit
|
|
86
|
+
just check-all # the kit, the playground and cargo check
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The playground links the sibling packages (rhizome, app-kit, preferences, facets, math), so `just dev` and `just check-all` need them checked out next to this repo. `just check` needs only this repo.
|
|
90
|
+
|
|
91
|
+
MIT.
|
package/package.json
CHANGED
|
@@ -1,6 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@preset.nz/ux-kit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "One look and one way of working for the preset.nz desktop apps: tokens, palettes, fonts and Base UI primitives.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/preset-nz/ux-kit.git"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"sideEffects": [
|
|
12
|
+
"**/*.css"
|
|
13
|
+
],
|
|
14
|
+
"exports": {
|
|
15
|
+
".": "./src/index.ts",
|
|
16
|
+
"./graphics": "./src/graphics/index.ts",
|
|
17
|
+
"./index.css": "./src/index.css"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"src"
|
|
21
|
+
],
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"react": "^19",
|
|
24
|
+
"react-dom": "^19"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@base-ui/react": "^1.6.0",
|
|
28
|
+
"@fontsource-variable/geist": "^5.2.9",
|
|
29
|
+
"@fontsource-variable/jetbrains-mono": "^5.2.8",
|
|
30
|
+
"@phosphor-icons/react": "^2.1.10",
|
|
31
|
+
"@preset.nz/math": "^0.1.0",
|
|
32
|
+
"class-variance-authority": "^0.7.1",
|
|
33
|
+
"clsx": "^2.1.1",
|
|
34
|
+
"shadcn": "^4.11.0",
|
|
35
|
+
"tailwind-merge": "^3.6.0",
|
|
36
|
+
"tw-animate-css": "^1.4.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@eslint/js": "^9.39.4",
|
|
40
|
+
"@types/react": "^19.2.17",
|
|
41
|
+
"@types/react-dom": "^19.2.3",
|
|
42
|
+
"eslint": "^9.39.4",
|
|
43
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
44
|
+
"eslint-plugin-react-refresh": "^0.5.3",
|
|
45
|
+
"globals": "^16.5.0",
|
|
46
|
+
"react": "^19.2.7",
|
|
47
|
+
"react-dom": "^19.2.7",
|
|
48
|
+
"typescript": "~5.9.3",
|
|
49
|
+
"typescript-eslint": "^8.61.1"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
}
|
|
6
54
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar"
|
|
2
|
+
|
|
3
|
+
import { cn } from "../lib/utils"
|
|
4
|
+
|
|
5
|
+
function Avatar({ className, ...props }: AvatarPrimitive.Root.Props) {
|
|
6
|
+
return (
|
|
7
|
+
<AvatarPrimitive.Root
|
|
8
|
+
data-slot="avatar"
|
|
9
|
+
className={cn("relative flex size-8 shrink-0 overflow-hidden rounded-full", className)}
|
|
10
|
+
{...props}
|
|
11
|
+
/>
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
|
|
16
|
+
return (
|
|
17
|
+
<AvatarPrimitive.Image
|
|
18
|
+
data-slot="avatar-image"
|
|
19
|
+
className={cn("aspect-square size-full", className)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function AvatarFallback({ className, ...props }: AvatarPrimitive.Fallback.Props) {
|
|
26
|
+
return (
|
|
27
|
+
<AvatarPrimitive.Fallback
|
|
28
|
+
data-slot="avatar-fallback"
|
|
29
|
+
className={cn("flex size-full items-center justify-center rounded-full bg-muted", className)}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { Avatar, AvatarImage, AvatarFallback }
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { mergeProps } from "@base-ui/react/merge-props"
|
|
2
|
+
import { useRender } from "@base-ui/react/use-render"
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../lib/utils"
|
|
6
|
+
|
|
7
|
+
const badgeVariants = cva(
|
|
8
|
+
"inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-md border px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: "border-transparent bg-primary text-primary-foreground [a]:hover:bg-primary/90",
|
|
13
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground [a]:hover:bg-secondary/90",
|
|
14
|
+
destructive:
|
|
15
|
+
"border-transparent bg-destructive text-destructive-foreground focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/90",
|
|
16
|
+
outline: "text-foreground [a]:hover:bg-accent [a]:hover:text-accent-foreground",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: {
|
|
20
|
+
variant: "default",
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
function Badge({
|
|
26
|
+
className,
|
|
27
|
+
variant = "default",
|
|
28
|
+
render,
|
|
29
|
+
...props
|
|
30
|
+
}: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
|
|
31
|
+
return useRender({
|
|
32
|
+
defaultTagName: "span",
|
|
33
|
+
props: mergeProps<"span">(
|
|
34
|
+
{ className: cn(badgeVariants({ variant }), className) },
|
|
35
|
+
props
|
|
36
|
+
),
|
|
37
|
+
render,
|
|
38
|
+
state: { slot: "badge", variant },
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { Badge, badgeVariants }
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { mergeProps } from "@base-ui/react/merge-props"
|
|
3
|
+
import { useRender } from "@base-ui/react/use-render"
|
|
4
|
+
import { CaretRightIcon, DotsThreeIcon } from "@phosphor-icons/react"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../lib/utils"
|
|
7
|
+
|
|
8
|
+
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
|
|
9
|
+
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
|
13
|
+
return (
|
|
14
|
+
<ol
|
|
15
|
+
data-slot="breadcrumb-list"
|
|
16
|
+
className={cn(
|
|
17
|
+
"flex flex-wrap items-center gap-1.5 text-sm break-words text-muted-foreground sm:gap-2.5",
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
|
|
26
|
+
return (
|
|
27
|
+
<li
|
|
28
|
+
data-slot="breadcrumb-item"
|
|
29
|
+
className={cn("inline-flex items-center gap-1.5", className)}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Plain anchor by default. Pass `render={<RouterLink to="..." />}` for a router link.
|
|
36
|
+
function BreadcrumbLink({
|
|
37
|
+
className,
|
|
38
|
+
render,
|
|
39
|
+
...props
|
|
40
|
+
}: useRender.ComponentProps<"a">) {
|
|
41
|
+
return useRender({
|
|
42
|
+
defaultTagName: "a",
|
|
43
|
+
props: mergeProps<"a">(
|
|
44
|
+
{ className: cn("transition-colors hover:text-foreground", className) },
|
|
45
|
+
props
|
|
46
|
+
),
|
|
47
|
+
render,
|
|
48
|
+
state: { slot: "breadcrumb-link" },
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
|
|
53
|
+
return (
|
|
54
|
+
<span
|
|
55
|
+
data-slot="breadcrumb-page"
|
|
56
|
+
role="link"
|
|
57
|
+
aria-disabled="true"
|
|
58
|
+
aria-current="page"
|
|
59
|
+
className={cn("font-normal text-foreground", className)}
|
|
60
|
+
{...props}
|
|
61
|
+
/>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function BreadcrumbSeparator({ children, className, ...props }: React.ComponentProps<"li">) {
|
|
66
|
+
return (
|
|
67
|
+
<li
|
|
68
|
+
data-slot="breadcrumb-separator"
|
|
69
|
+
role="presentation"
|
|
70
|
+
aria-hidden="true"
|
|
71
|
+
className={cn("[&>svg]:size-3.5", className)}
|
|
72
|
+
{...props}
|
|
73
|
+
>
|
|
74
|
+
{children ?? <CaretRightIcon />}
|
|
75
|
+
</li>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function BreadcrumbEllipsis({ className, ...props }: React.ComponentProps<"span">) {
|
|
80
|
+
return (
|
|
81
|
+
<span
|
|
82
|
+
data-slot="breadcrumb-ellipsis"
|
|
83
|
+
role="presentation"
|
|
84
|
+
aria-hidden="true"
|
|
85
|
+
className={cn("flex size-9 items-center justify-center", className)}
|
|
86
|
+
{...props}
|
|
87
|
+
>
|
|
88
|
+
<DotsThreeIcon className="size-4" />
|
|
89
|
+
<span className="sr-only">More</span>
|
|
90
|
+
</span>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export {
|
|
95
|
+
Breadcrumb,
|
|
96
|
+
BreadcrumbList,
|
|
97
|
+
BreadcrumbItem,
|
|
98
|
+
BreadcrumbLink,
|
|
99
|
+
BreadcrumbPage,
|
|
100
|
+
BreadcrumbSeparator,
|
|
101
|
+
BreadcrumbEllipsis,
|
|
102
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Button as ButtonPrimitive } from "@base-ui/react/button"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
|
|
4
|
+
import { cn } from "../lib/utils"
|
|
5
|
+
|
|
6
|
+
const buttonVariants = cva(
|
|
7
|
+
"group/button inline-flex shrink-0 items-center justify-center rounded-sm border border-transparent bg-clip-padding font-heading text-xs font-medium tracking-wide uppercase whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
|
12
|
+
outline:
|
|
13
|
+
"border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
|
14
|
+
secondary:
|
|
15
|
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
16
|
+
ghost:
|
|
17
|
+
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
|
18
|
+
destructive:
|
|
19
|
+
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
|
20
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
21
|
+
},
|
|
22
|
+
size: {
|
|
23
|
+
default:
|
|
24
|
+
"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
25
|
+
xs: "h-6 gap-1 rounded-sm px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
26
|
+
sm: "h-7 gap-1 rounded-sm px-2.5 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
27
|
+
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
28
|
+
icon: "size-8",
|
|
29
|
+
"icon-xs": "size-6 rounded-sm [&_svg:not([class*='size-'])]:size-3",
|
|
30
|
+
"icon-sm": "size-7 rounded-sm",
|
|
31
|
+
"icon-lg": "size-9",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
defaultVariants: {
|
|
35
|
+
variant: "default",
|
|
36
|
+
size: "default",
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
function Button({
|
|
42
|
+
className,
|
|
43
|
+
variant = "default",
|
|
44
|
+
size = "default",
|
|
45
|
+
...props
|
|
46
|
+
}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
|
|
47
|
+
return (
|
|
48
|
+
<ButtonPrimitive
|
|
49
|
+
data-slot="button"
|
|
50
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { Button, buttonVariants }
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "../lib/utils"
|
|
4
|
+
|
|
5
|
+
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
data-slot="card"
|
|
9
|
+
className={cn(
|
|
10
|
+
"bg-card text-card-foreground flex flex-col gap-4 rounded-xl border py-4 shadow-sm",
|
|
11
|
+
className,
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
data-slot="card-header"
|
|
22
|
+
className={cn(
|
|
23
|
+
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-4",
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
data-slot="card-title"
|
|
35
|
+
className={cn("leading-none font-semibold", className)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
data-slot="card-description"
|
|
45
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
52
|
+
return (
|
|
53
|
+
<div
|
|
54
|
+
data-slot="card-action"
|
|
55
|
+
className={cn(
|
|
56
|
+
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
57
|
+
className,
|
|
58
|
+
)}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
65
|
+
return (
|
|
66
|
+
<div
|
|
67
|
+
data-slot="card-content"
|
|
68
|
+
className={cn("px-4", className)}
|
|
69
|
+
{...props}
|
|
70
|
+
/>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
75
|
+
return (
|
|
76
|
+
<div
|
|
77
|
+
data-slot="card-footer"
|
|
78
|
+
className={cn("flex items-center px-4 [.border-t]:pt-4", className)}
|
|
79
|
+
{...props}
|
|
80
|
+
/>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export {
|
|
85
|
+
Card,
|
|
86
|
+
CardHeader,
|
|
87
|
+
CardFooter,
|
|
88
|
+
CardTitle,
|
|
89
|
+
CardAction,
|
|
90
|
+
CardDescription,
|
|
91
|
+
CardContent,
|
|
92
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox"
|
|
2
|
+
|
|
3
|
+
import { cn } from "../lib/utils"
|
|
4
|
+
import { CheckIcon } from "@phosphor-icons/react"
|
|
5
|
+
|
|
6
|
+
function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {
|
|
7
|
+
return (
|
|
8
|
+
<CheckboxPrimitive.Root
|
|
9
|
+
data-slot="checkbox"
|
|
10
|
+
className={cn(
|
|
11
|
+
"peer relative flex size-4 shrink-0 items-center justify-center rounded-none border border-input transition-colors outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary",
|
|
12
|
+
className
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
>
|
|
16
|
+
<CheckboxPrimitive.Indicator
|
|
17
|
+
data-slot="checkbox-indicator"
|
|
18
|
+
className="grid place-content-center text-current transition-none [&>svg]:size-3.5"
|
|
19
|
+
>
|
|
20
|
+
<CheckIcon
|
|
21
|
+
/>
|
|
22
|
+
</CheckboxPrimitive.Indicator>
|
|
23
|
+
</CheckboxPrimitive.Root>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { Checkbox }
|