@ponti-studios/ui 0.7.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/package.json +22 -20
- package/src/components/data-display/accordion.tsx +1 -1
- package/src/components/data-display/sort-controls.tsx +3 -3
- package/src/components/feedback/index.ts +0 -2
- package/src/components/forms/date-month-select.tsx +12 -3
- package/src/components/forms/date-picker.tsx +16 -12
- package/src/components/forms/drop-zone.tsx +1 -1
- package/src/components/forms/index.ts +4 -8
- package/src/components/forms/passkey-management.tsx +4 -10
- package/src/components/forms/radio-group.tsx +8 -4
- package/src/components/forms/select.tsx +1 -1
- package/src/components/forms/text-field.tsx +40 -5
- package/src/components/navigation/app-navigation.stories.tsx +111 -21
- package/src/components/navigation/app-navigation.tsx +165 -137
- package/src/components/navigation/index.ts +8 -4
- package/src/components/navigation/pagination-controls.stories.tsx +1 -1
- package/src/components/navigation/pagination-controls.tsx +10 -14
- package/src/components/overlays/dropdown-menu.stories.tsx +2 -2
- package/src/components/primitives/card.tsx +6 -10
- package/src/components/primitives/label.stories.tsx +0 -18
- package/src/components/primitives/label.tsx +12 -12
- package/src/constants/chart-colors.ts +16 -16
- package/src/lib/slot.tsx +29 -0
- package/src/styles/animations.css +0 -22
- package/src/styles/index.css +60 -6
- package/src/styles/tokens/color-systems.stories.tsx +2 -1
- package/src/styles/tokens/generated/light.colors.generated.ts +3 -3
- package/src/styles/tokens/generated/light.css +3 -3
- package/src/styles/tokens/source/light.tokens.json +3 -3
- package/src/styles/tokens/style-dictionary.light-storybook.json +16 -0
- package/src/components/feedback/file-upload-status-badge.stories.tsx +0 -28
- package/src/components/feedback/file-upload-status-badge.tsx +0 -33
- package/src/components/forms/field.stories.tsx +0 -62
- package/src/components/forms/field.tsx +0 -65
- package/src/components/forms/field.types.ts +0 -7
- package/src/components/forms/group-by-select.stories.tsx +0 -26
- package/src/components/forms/group-by-select.tsx +0 -47
- package/src/components/navigation/route-link.stories.tsx +0 -30
- package/src/components/navigation/route-link.tsx +0 -42
- package/src/hooks/use-derived-filter-state.stories.tsx +0 -136
- package/src/hooks/use-filter-state.stories.tsx +0 -102
package/README.md
CHANGED
|
@@ -11,6 +11,19 @@ The design system is documented in `docs/`:
|
|
|
11
11
|
- [Patterns](./docs/patterns.md) — screen composition rules
|
|
12
12
|
- [Review gates](./docs/review-gates.md) — pass/fail review criteria
|
|
13
13
|
|
|
14
|
+
## Development
|
|
15
|
+
|
|
16
|
+
Common development workflows are available through the `Makefile`:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
make check # tokens, source contracts, and typecheck
|
|
20
|
+
make tokens-build # regenerate token artifacts
|
|
21
|
+
make storybook # build Storybook themes and start Storybook
|
|
22
|
+
make test-a11y # run light and dark accessibility tests
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The existing `pnpm run` scripts delegate to these targets for compatibility.
|
|
26
|
+
|
|
14
27
|
## Release
|
|
15
28
|
|
|
16
29
|
Releases are owned by [release-please](https://github.com/googleapis/release-please), not by hand-edited version numbers or manually pushed tags. The `.github/workflows/publish.yml` workflow runs after a successful `validate` push to `main`:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ponti-studios/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Shared UI component library for Ponti Studios",
|
|
6
6
|
"license": "MIT",
|
|
@@ -82,18 +82,20 @@
|
|
|
82
82
|
"registry": "https://registry.npmjs.org"
|
|
83
83
|
},
|
|
84
84
|
"scripts": {
|
|
85
|
-
"build": "
|
|
85
|
+
"build": "make build",
|
|
86
86
|
"dev": "vite",
|
|
87
|
-
"storybook": "
|
|
88
|
-
"check:source": "
|
|
89
|
-
"test": "
|
|
90
|
-
"test:a11y": "
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
"
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
"
|
|
87
|
+
"storybook": "make storybook",
|
|
88
|
+
"check:source": "make check-source",
|
|
89
|
+
"test": "make test",
|
|
90
|
+
"test:a11y": "make test-a11y",
|
|
91
|
+
"test:a11y:light": "make test-a11y-light",
|
|
92
|
+
"test:a11y:dark": "make test-a11y-dark",
|
|
93
|
+
"tokens:build": "make tokens-build",
|
|
94
|
+
"tokens:check": "make tokens-check",
|
|
95
|
+
"typecheck": "make typecheck",
|
|
96
|
+
"lint": "make lint",
|
|
97
|
+
"format": "make format",
|
|
98
|
+
"prepack": "make prepack"
|
|
97
99
|
},
|
|
98
100
|
"dependencies": {
|
|
99
101
|
"@base-ui/react": "^1.6.0",
|
|
@@ -107,13 +109,13 @@
|
|
|
107
109
|
},
|
|
108
110
|
"devDependencies": {
|
|
109
111
|
"@chromatic-com/storybook": "^5.2.1",
|
|
110
|
-
"@storybook/addon-a11y": "10.5.
|
|
111
|
-
"@storybook/addon-docs": "
|
|
112
|
-
"@storybook/addon-links": "
|
|
113
|
-
"@storybook/addon-onboarding": "
|
|
114
|
-
"@storybook/addon-themes": "10.5.
|
|
115
|
-
"@storybook/addon-vitest": "10.5.
|
|
116
|
-
"@storybook/react-vite": "
|
|
112
|
+
"@storybook/addon-a11y": "10.5.6",
|
|
113
|
+
"@storybook/addon-docs": "10.5.6",
|
|
114
|
+
"@storybook/addon-links": "10.5.6",
|
|
115
|
+
"@storybook/addon-onboarding": "10.5.6",
|
|
116
|
+
"@storybook/addon-themes": "10.5.6",
|
|
117
|
+
"@storybook/addon-vitest": "10.5.6",
|
|
118
|
+
"@storybook/react-vite": "10.5.6",
|
|
117
119
|
"@tailwindcss/vite": "^4.3.3",
|
|
118
120
|
"@types/node": "^26.1.1",
|
|
119
121
|
"@types/react": "^19.2.17",
|
|
@@ -127,7 +129,7 @@
|
|
|
127
129
|
"react": "^19.2.7",
|
|
128
130
|
"react-dom": "^19.2.7",
|
|
129
131
|
"react-native": "0.86.0",
|
|
130
|
-
"storybook": "
|
|
132
|
+
"storybook": "10.5.6",
|
|
131
133
|
"style-dictionary": "5.5.0",
|
|
132
134
|
"tailwindcss": "^4.3.3",
|
|
133
135
|
"typescript": "6.0.3",
|
|
@@ -46,7 +46,7 @@ function AccordionTrigger({
|
|
|
46
46
|
<AccordionPrimitive.Trigger
|
|
47
47
|
data-slot="accordion-trigger"
|
|
48
48
|
className={cn(
|
|
49
|
-
"flex w-full flex-1 items-center justify-between gap-4 px-4 py-3 text-left text-sm font-semibold disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
|
49
|
+
"text-muted-foreground flex w-full flex-1 items-center justify-between gap-4 px-4 py-3 text-left text-sm font-semibold disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
|
50
50
|
className,
|
|
51
51
|
)}
|
|
52
52
|
{...props}
|
|
@@ -48,10 +48,10 @@ export function SortControls({
|
|
|
48
48
|
</Button>
|
|
49
49
|
</DropdownMenuTrigger>
|
|
50
50
|
<DropdownMenuContent className="w-80 space-y-2 p-2">
|
|
51
|
-
<DropdownMenuLabel>Define Sort Order</DropdownMenuLabel>
|
|
52
|
-
<DropdownMenuSeparator />
|
|
53
|
-
|
|
54
51
|
<DropdownMenuGroup className="space-y-1">
|
|
52
|
+
<DropdownMenuLabel>Define Sort Order</DropdownMenuLabel>
|
|
53
|
+
<DropdownMenuSeparator />
|
|
54
|
+
|
|
55
55
|
{sortOptions.map((sort, index) => {
|
|
56
56
|
const usedFields = sortOptions
|
|
57
57
|
.filter((_, i) => i !== index)
|
|
@@ -3,8 +3,6 @@ export { EmptyState } from "./empty-state";
|
|
|
3
3
|
export type { EmptyStateProps } from "./empty-state";
|
|
4
4
|
export { FileUploadStatus } from "./file-upload-status";
|
|
5
5
|
export type { FileUploadState, FileUploadStats } from "./file-upload-status";
|
|
6
|
-
export { FileUploadStatusBadge } from "./file-upload-status-badge";
|
|
7
|
-
export type { FileUploadStatusValue } from "./file-upload-status-badge";
|
|
8
6
|
export { Progress } from "./progress";
|
|
9
7
|
export { ProgressBar } from "./progress-bar";
|
|
10
8
|
export type { ProgressBarProps } from "./progress-bar";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Calendar } from "lucide-react";
|
|
2
2
|
import { useMemo } from "react";
|
|
3
3
|
|
|
4
|
+
import { cn } from "../../lib/utils";
|
|
4
5
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select";
|
|
5
6
|
|
|
6
7
|
interface MonthOption {
|
|
@@ -40,13 +41,21 @@ export function DateMonthSelect({
|
|
|
40
41
|
}, [monthsBack]);
|
|
41
42
|
|
|
42
43
|
return (
|
|
43
|
-
<div className="flex items-center
|
|
44
|
-
<
|
|
44
|
+
<div className="flex items-center">
|
|
45
|
+
<div className="flex items-center justify-center size-10 border rounded-l-xl bg-surface-2 text-text-1">
|
|
46
|
+
<Calendar className="size-4" />
|
|
47
|
+
</div>
|
|
45
48
|
<Select
|
|
46
49
|
value={selectedMonthYear}
|
|
47
50
|
onValueChange={(v: string | null) => v != null && onMonthChange(v)}
|
|
48
51
|
>
|
|
49
|
-
<SelectTrigger
|
|
52
|
+
<SelectTrigger
|
|
53
|
+
className={cn(
|
|
54
|
+
"rounded-r-xl rounded-l-none min-h-10 max-h-10 bg-surface-2 text-text-1 border-l-0",
|
|
55
|
+
className,
|
|
56
|
+
)}
|
|
57
|
+
aria-label={placeholder}
|
|
58
|
+
>
|
|
50
59
|
<SelectValue placeholder={placeholder} />
|
|
51
60
|
</SelectTrigger>
|
|
52
61
|
<SelectContent>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { format } from "date-fns";
|
|
2
2
|
import { CalendarIcon } from "lucide-react";
|
|
3
3
|
|
|
4
|
-
import { Button } from "../primitives/button";
|
|
5
4
|
import { Calendar } from "../forms/calendar";
|
|
6
5
|
import { Popover, PopoverContent, PopoverTrigger } from "../overlays/popover";
|
|
6
|
+
import { Button } from "../primitives/button";
|
|
7
7
|
|
|
8
8
|
export interface DatePickerProps {
|
|
9
9
|
value: Date | undefined;
|
|
@@ -40,17 +40,21 @@ export function DatePicker({
|
|
|
40
40
|
</label>
|
|
41
41
|
) : null}
|
|
42
42
|
<Popover>
|
|
43
|
-
<
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
43
|
+
<div className="flex w-full items-center">
|
|
44
|
+
<div className="border-border bg-surface-2 text-text-1 flex size-10 shrink-0 items-center justify-center rounded-l-xl border">
|
|
45
|
+
<CalendarIcon className="size-4" aria-hidden />
|
|
46
|
+
</div>
|
|
47
|
+
<PopoverTrigger asChild>
|
|
48
|
+
<Button
|
|
49
|
+
id={id}
|
|
50
|
+
variant={variant}
|
|
51
|
+
disabled={disabled}
|
|
52
|
+
className="border-border bg-surface-2 text-text-1 placeholder:text-muted-foreground min-h-10 max-h-10 w-full justify-start rounded-l-none rounded-r-xl border-l-0 text-left font-normal"
|
|
53
|
+
>
|
|
54
|
+
{value ? format(value, dateFormat) : <span>{placeholder}</span>}
|
|
55
|
+
</Button>
|
|
56
|
+
</PopoverTrigger>
|
|
57
|
+
</div>
|
|
54
58
|
<PopoverContent className="bg-popover w-auto p-0" align={popoverAlign}>
|
|
55
59
|
<Calendar mode="single" selected={value} onSelect={onSelect} />
|
|
56
60
|
</PopoverContent>
|
|
@@ -126,7 +126,7 @@ export function DropZone({
|
|
|
126
126
|
>
|
|
127
127
|
<div className="space-y-2 text-center">
|
|
128
128
|
<p className="text-muted-foreground">
|
|
129
|
-
{label} <span className="text-
|
|
129
|
+
{label} <span className="text-accent font-medium">click to browse</span>
|
|
130
130
|
</p>
|
|
131
131
|
{helpText ? <p className="text-muted-foreground text-sm">{helpText}</p> : null}
|
|
132
132
|
</div>
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
+
export { FilterSelect } from "../filters/filter-select";
|
|
2
|
+
export type { FilterSelectOption, FilterSelectProps } from "../filters/filter-select";
|
|
1
3
|
export { Calendar } from "./calendar";
|
|
2
4
|
export type { CalendarProps } from "./calendar";
|
|
3
|
-
export { DatePicker } from "./date-picker";
|
|
4
|
-
export type { DatePickerProps } from "./date-picker";
|
|
5
5
|
export { DateMonthSelect, getCurrentMonthYear } from "./date-month-select";
|
|
6
6
|
export type { DateMonthSelectProps } from "./date-month-select";
|
|
7
|
+
export { DatePicker } from "./date-picker";
|
|
8
|
+
export type { DatePickerProps } from "./date-picker";
|
|
7
9
|
export { DropZone } from "./drop-zone";
|
|
8
10
|
export type { DropZoneProps } from "./drop-zone";
|
|
9
|
-
export { Field } from "./field";
|
|
10
|
-
export type { FieldProps } from "./field";
|
|
11
|
-
export { FilterSelect } from "../filters/filter-select";
|
|
12
|
-
export type { FilterSelectOption, FilterSelectProps } from "../filters/filter-select";
|
|
13
|
-
export { GroupBySelect } from "./group-by-select";
|
|
14
|
-
export type { GroupBySelectProps } from "./group-by-select";
|
|
15
11
|
export { Input } from "./input";
|
|
16
12
|
export { PasskeyManagement } from "./passkey-management";
|
|
17
13
|
export type { PasskeyManagementProps, PasskeyRecord } from "./passkey-management";
|
|
@@ -68,17 +68,11 @@ export function PasskeyManagement({
|
|
|
68
68
|
return (
|
|
69
69
|
<section aria-labelledby="passkey-heading" className="space-y-4">
|
|
70
70
|
<div className="flex items-center justify-between">
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
</h2>
|
|
75
|
-
<p className="text-muted-foreground text-sm">
|
|
76
|
-
Sign in without a password using biometrics or a security key.
|
|
77
|
-
</p>
|
|
78
|
-
</div>
|
|
71
|
+
<h2 id="passkey-heading" className="text-foreground text-sm font-medium">
|
|
72
|
+
Passkeys
|
|
73
|
+
</h2>
|
|
79
74
|
<Button
|
|
80
75
|
type="button"
|
|
81
|
-
variant="outline"
|
|
82
76
|
size="sm"
|
|
83
77
|
onClick={() => void handleAdd()}
|
|
84
78
|
disabled={adding}
|
|
@@ -107,7 +101,7 @@ export function PasskeyManagement({
|
|
|
107
101
|
{passkeys.map((pk) => (
|
|
108
102
|
<li
|
|
109
103
|
key={pk.id}
|
|
110
|
-
className="border-border flex items-center justify-between border px-4 py-3 text-sm"
|
|
104
|
+
className="border-border rounded flex items-center justify-between border px-4 py-3 text-sm"
|
|
111
105
|
>
|
|
112
106
|
<div className="flex items-center gap-3">
|
|
113
107
|
<KeyRound className="text-muted-foreground size-4 shrink-0" aria-hidden />
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group";
|
|
4
3
|
import { Radio as RadioPrimitive } from "@base-ui/react/radio";
|
|
4
|
+
import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group";
|
|
5
5
|
import { Circle } from "lucide-react";
|
|
6
6
|
import * as React from "react";
|
|
7
7
|
|
|
@@ -23,13 +23,17 @@ const RadioGroupItem = React.forwardRef<
|
|
|
23
23
|
<RadioPrimitive.Root
|
|
24
24
|
ref={ref}
|
|
25
25
|
className={cn(
|
|
26
|
-
"border-primary bg-background
|
|
26
|
+
"border-primary bg-background focus-visible:border-ring focus-visible:bg-muted/25 focus-visible:ring-ring aspect-square size-6 rounded-full border focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
|
27
|
+
"flex items-center justify-center",
|
|
27
28
|
className,
|
|
28
29
|
)}
|
|
29
30
|
{...props}
|
|
30
31
|
>
|
|
31
|
-
<RadioPrimitive.Indicator
|
|
32
|
-
|
|
32
|
+
<RadioPrimitive.Indicator
|
|
33
|
+
keepMounted
|
|
34
|
+
className="data-unchecked:opacity-0 data-checked:opacity-100 data-starting-style:animate-fade-in data-ending-style:animate-fade-out"
|
|
35
|
+
>
|
|
36
|
+
<Circle className="fill-primary size-5" />
|
|
33
37
|
</RadioPrimitive.Indicator>
|
|
34
38
|
</RadioPrimitive.Root>
|
|
35
39
|
);
|
|
@@ -71,7 +71,7 @@ function SelectContent({
|
|
|
71
71
|
<SelectPrimitive.Popup
|
|
72
72
|
data-slot="select-content"
|
|
73
73
|
className={cn(
|
|
74
|
-
"bg-
|
|
74
|
+
"bg-background text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:zoom-out-95 data-open:zoom-in-95 relative z-50 max-h-(--available-height) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 outline-none",
|
|
75
75
|
className,
|
|
76
76
|
)}
|
|
77
77
|
{...props}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
4
|
import { Input } from "./input";
|
|
5
5
|
import type { TextFieldBaseProps, TextFieldType } from "./text-field.types";
|
|
6
6
|
|
|
@@ -26,14 +26,49 @@ function TextField({
|
|
|
26
26
|
type = "text",
|
|
27
27
|
...inputProps
|
|
28
28
|
}: TextFieldProps) {
|
|
29
|
+
const generatedId = React.useId();
|
|
30
|
+
const inputId = id ?? generatedId;
|
|
31
|
+
const descriptionId = `${inputId}-description`;
|
|
32
|
+
const errorId = `${inputId}-error`;
|
|
33
|
+
|
|
29
34
|
if (!label && !helpText && !error) {
|
|
30
|
-
return
|
|
35
|
+
return (
|
|
36
|
+
<Input id={inputId} className={className} disabled={disabled} type={type} {...inputProps} />
|
|
37
|
+
);
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
return (
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
<div className="flex flex-col gap-1.5">
|
|
42
|
+
{label && (
|
|
43
|
+
<label
|
|
44
|
+
htmlFor={inputId}
|
|
45
|
+
className={cn(
|
|
46
|
+
"text-foreground text-sm font-medium",
|
|
47
|
+
inputProps.required && "after:text-destructive-text after:ml-0.5 after:content-['*']",
|
|
48
|
+
)}
|
|
49
|
+
>
|
|
50
|
+
{label}
|
|
51
|
+
</label>
|
|
52
|
+
)}
|
|
53
|
+
<Input
|
|
54
|
+
id={inputId}
|
|
55
|
+
className={className}
|
|
56
|
+
disabled={disabled}
|
|
57
|
+
type={type}
|
|
58
|
+
aria-describedby={error ? errorId : helpText ? descriptionId : undefined}
|
|
59
|
+
aria-invalid={error ? true : undefined}
|
|
60
|
+
{...inputProps}
|
|
61
|
+
/>
|
|
62
|
+
{error ? (
|
|
63
|
+
<p id={errorId} role="alert" className="text-destructive-text text-sm">
|
|
64
|
+
{error}
|
|
65
|
+
</p>
|
|
66
|
+
) : helpText ? (
|
|
67
|
+
<p id={descriptionId} className="text-muted-foreground text-sm">
|
|
68
|
+
{helpText}
|
|
69
|
+
</p>
|
|
70
|
+
) : null}
|
|
71
|
+
</div>
|
|
37
72
|
);
|
|
38
73
|
}
|
|
39
74
|
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { Button } from "../primitives/button";
|
|
5
|
+
import { Navigation } from "./app-navigation";
|
|
5
6
|
|
|
6
|
-
const meta: Meta<typeof
|
|
7
|
+
const meta: Meta<typeof Navigation> = {
|
|
7
8
|
title: "Navigation/AppNavigation",
|
|
8
|
-
component:
|
|
9
|
+
component: Navigation,
|
|
9
10
|
tags: ["autodocs"],
|
|
10
11
|
parameters: {
|
|
11
12
|
layout: "fullscreen",
|
|
12
13
|
},
|
|
13
14
|
};
|
|
14
|
-
|
|
15
15
|
export default meta;
|
|
16
16
|
type Story = StoryObj<typeof meta>;
|
|
17
17
|
|
|
@@ -19,7 +19,7 @@ type Story = StoryObj<typeof meta>;
|
|
|
19
19
|
// Shared helpers
|
|
20
20
|
// ---------------------------------------------------------------------------
|
|
21
21
|
|
|
22
|
-
const defaultLinks
|
|
22
|
+
const defaultLinks = [
|
|
23
23
|
{ href: "/features", label: "Features" },
|
|
24
24
|
{ href: "/pricing", label: "Pricing" },
|
|
25
25
|
{ href: "/blog", label: "Blog" },
|
|
@@ -46,12 +46,25 @@ function PageContent({
|
|
|
46
46
|
export const Default: Story = {
|
|
47
47
|
render: () => (
|
|
48
48
|
<>
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
<Navigation>
|
|
50
|
+
<Navigation.Brand>
|
|
51
|
+
<a href="/">Acme</a>
|
|
52
|
+
</Navigation.Brand>
|
|
53
|
+
|
|
54
|
+
<Navigation.List>
|
|
55
|
+
{defaultLinks.map((link) => (
|
|
56
|
+
<Navigation.Item key={link.href} href={link.href}>
|
|
57
|
+
{link.label}
|
|
58
|
+
</Navigation.Item>
|
|
59
|
+
))}
|
|
60
|
+
</Navigation.List>
|
|
61
|
+
|
|
62
|
+
<Navigation.Action>
|
|
63
|
+
<Button size="md" asChild>
|
|
64
|
+
<a href="/get-started">Get started</a>
|
|
65
|
+
</Button>
|
|
66
|
+
</Navigation.Action>
|
|
67
|
+
</Navigation>
|
|
55
68
|
<PageContent />
|
|
56
69
|
</>
|
|
57
70
|
),
|
|
@@ -60,22 +73,99 @@ export const Default: Story = {
|
|
|
60
73
|
export const NoCta: Story = {
|
|
61
74
|
render: () => (
|
|
62
75
|
<>
|
|
63
|
-
<
|
|
64
|
-
|
|
76
|
+
<Navigation>
|
|
77
|
+
<Navigation.Brand>
|
|
78
|
+
<a href="/">Acme</a>
|
|
79
|
+
</Navigation.Brand>
|
|
80
|
+
<Navigation.List>
|
|
81
|
+
{defaultLinks.map((link) => (
|
|
82
|
+
<Navigation.Item key={link.href} href={link.href}>
|
|
83
|
+
{link.label}
|
|
84
|
+
</Navigation.Item>
|
|
85
|
+
))}
|
|
86
|
+
</Navigation.List>
|
|
87
|
+
</Navigation>
|
|
88
|
+
<PageContent message="Same nav, no Action content." />
|
|
65
89
|
</>
|
|
66
90
|
),
|
|
67
91
|
};
|
|
68
92
|
|
|
69
|
-
|
|
93
|
+
/**
|
|
94
|
+
* `Navigation.Item` (and any of the other pieces) support the same
|
|
95
|
+
* `asChild` composition as `Button`: pass your router's `Link` (or
|
|
96
|
+
* anything else) as the single child. This story stands in for a router
|
|
97
|
+
* `Link` with a plain anchor that no-ops on click, since Storybook has no
|
|
98
|
+
* router.
|
|
99
|
+
*/
|
|
100
|
+
export const WithCustomRouterLink: Story = {
|
|
101
|
+
render: () => {
|
|
102
|
+
const activeHref = "/pricing";
|
|
103
|
+
return (
|
|
104
|
+
<>
|
|
105
|
+
<Navigation>
|
|
106
|
+
<Navigation.Brand>
|
|
107
|
+
<a href="/" data-router-link="stand-in" onClick={(e) => e.preventDefault()}>
|
|
108
|
+
Acme
|
|
109
|
+
</a>
|
|
110
|
+
</Navigation.Brand>
|
|
111
|
+
|
|
112
|
+
<Navigation.List>
|
|
113
|
+
{defaultLinks.map((link) => (
|
|
114
|
+
<Navigation.Item key={link.href} asChild active={link.href === activeHref}>
|
|
115
|
+
<a href={link.href} data-router-link="stand-in" onClick={(e) => e.preventDefault()}>
|
|
116
|
+
{link.label}
|
|
117
|
+
</a>
|
|
118
|
+
</Navigation.Item>
|
|
119
|
+
))}
|
|
120
|
+
</Navigation.List>
|
|
121
|
+
|
|
122
|
+
<Navigation.Action>
|
|
123
|
+
<Button size="md" asChild>
|
|
124
|
+
<a href="/get-started" data-router-link="stand-in" onClick={(e) => e.preventDefault()}>
|
|
125
|
+
Get started
|
|
126
|
+
</a>
|
|
127
|
+
</Button>
|
|
128
|
+
</Navigation.Action>
|
|
129
|
+
</Navigation>
|
|
130
|
+
<PageContent message="Every link here is a custom element composed via asChild - open the elements panel to see data-router-link on each one, and the /pricing item's data-active/aria-current." />
|
|
131
|
+
</>
|
|
132
|
+
);
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Composition means arbitrary content can sit alongside the link list and
|
|
138
|
+
* action area without any prop-shape changes to `Navigation` itself.
|
|
139
|
+
*/
|
|
140
|
+
export const WithExtraContent: Story = {
|
|
70
141
|
render: () => (
|
|
71
142
|
<>
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
143
|
+
<Navigation>
|
|
144
|
+
<Navigation.Brand>
|
|
145
|
+
<a href="/">Acme</a>
|
|
146
|
+
</Navigation.Brand>
|
|
147
|
+
|
|
148
|
+
<Navigation.List>
|
|
149
|
+
{defaultLinks.slice(0, 2).map((link) => (
|
|
150
|
+
<Navigation.Item key={link.href} href={link.href}>
|
|
151
|
+
{link.label}
|
|
152
|
+
</Navigation.Item>
|
|
153
|
+
))}
|
|
154
|
+
</Navigation.List>
|
|
155
|
+
|
|
156
|
+
<input
|
|
157
|
+
type="search"
|
|
158
|
+
placeholder="Search..."
|
|
159
|
+
className="h-9 w-40 rounded-md border bg-background px-3 text-sm"
|
|
160
|
+
/>
|
|
161
|
+
|
|
162
|
+
<Navigation.Action>
|
|
163
|
+
<Button size="md" variant="outline" asChild>
|
|
164
|
+
<a href="/get-started">Get started</a>
|
|
165
|
+
</Button>
|
|
166
|
+
</Navigation.Action>
|
|
167
|
+
</Navigation>
|
|
168
|
+
<PageContent message="A search input sits directly between the link list and the action area — no endContent prop needed." />
|
|
79
169
|
</>
|
|
80
170
|
),
|
|
81
171
|
};
|