@nous-research/ui 0.16.0 → 0.17.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/CHANGELOG.md +39 -0
- package/dist/hooks/use-below-breakpoint.d.ts +2 -0
- package/dist/hooks/use-below-breakpoint.js +17 -0
- package/dist/hooks/use-confirm-delete.d.ts +10 -0
- package/dist/hooks/use-confirm-delete.js +35 -0
- package/dist/hooks/use-toast.d.ts +7 -0
- package/dist/hooks/use-toast.js +21 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.js +23 -2
- package/dist/ui/components/bottom-sheet.d.ts +15 -0
- package/dist/ui/components/bottom-sheet.js +192 -0
- package/dist/ui/components/card.d.ts +5 -0
- package/dist/ui/components/card.js +74 -0
- package/dist/ui/components/checkbox.d.ts +1 -1
- package/dist/ui/components/checkbox.js +2 -2
- package/dist/ui/components/confirm-dialog.d.ts +13 -0
- package/dist/ui/components/confirm-dialog.js +113 -0
- package/dist/ui/components/dialog.d.ts +15 -0
- package/dist/ui/components/dialog.js +171 -0
- package/dist/ui/components/input.d.ts +1 -0
- package/dist/ui/components/input.js +21 -0
- package/dist/ui/components/label.d.ts +1 -0
- package/dist/ui/components/label.js +18 -0
- package/dist/ui/components/separator.d.ts +5 -0
- package/dist/ui/components/separator.js +22 -0
- package/dist/ui/components/toast.d.ts +8 -0
- package/dist/ui/components/toast.js +39 -0
- package/dist/ui/globals.css +14 -2
- package/package.json +2 -2
- package/src/hooks/use-below-breakpoint.ts +21 -0
- package/src/hooks/use-confirm-delete.ts +43 -0
- package/src/hooks/use-toast.ts +29 -0
- package/src/index.ts +22 -1
- package/src/ui/components/animated-count.stories.tsx +1 -1
- package/src/ui/components/ascii.stories.tsx +1 -1
- package/src/ui/components/badge.stories.tsx +1 -1
- package/src/ui/components/blend-mode.stories.tsx +1 -1
- package/src/ui/components/blink.stories.tsx +1 -1
- package/src/ui/components/bottom-sheet.stories.tsx +43 -0
- package/src/ui/components/bottom-sheet.tsx +227 -0
- package/src/ui/components/button.stories.tsx +1 -1
- package/src/ui/components/card.stories.tsx +63 -0
- package/src/ui/components/card.tsx +85 -0
- package/src/ui/components/checkbox.stories.tsx +1 -1
- package/src/ui/components/checkbox.tsx +1 -1
- package/src/ui/components/command-block.stories.tsx +1 -1
- package/src/ui/components/confirm-dialog.stories.tsx +91 -0
- package/src/ui/components/confirm-dialog.tsx +130 -0
- package/src/ui/components/dialog.stories.tsx +169 -0
- package/src/ui/components/dialog.tsx +177 -0
- package/src/ui/components/dropdown-menu.stories.tsx +1 -1
- package/src/ui/components/fit-text/index.stories.tsx +1 -1
- package/src/ui/components/forms.stories.tsx +173 -0
- package/src/ui/components/graphs/index.stories.tsx +1 -1
- package/src/ui/components/hover-bg.stories.tsx +1 -1
- package/src/ui/components/image-distortion.stories.tsx +1 -1
- package/src/ui/components/input.stories.tsx +39 -0
- package/src/ui/components/input.tsx +20 -0
- package/src/ui/components/label.stories.tsx +26 -0
- package/src/ui/components/label.tsx +16 -0
- package/src/ui/components/list-item.stories.tsx +1 -1
- package/src/ui/components/poster.stories.tsx +1 -1
- package/src/ui/components/progress.stories.tsx +1 -1
- package/src/ui/components/scramble.stories.tsx +1 -1
- package/src/ui/components/segmented.stories.tsx +1 -1
- package/src/ui/components/select.stories.tsx +1 -1
- package/src/ui/components/separator.stories.tsx +33 -0
- package/src/ui/components/separator.tsx +24 -0
- package/src/ui/components/spinner.stories.tsx +1 -1
- package/src/ui/components/stats.stories.tsx +1 -1
- package/src/ui/components/switch.stories.tsx +1 -1
- package/src/ui/components/tabs.stories.tsx +1 -1
- package/src/ui/components/terminal-demo.stories.tsx +1 -1
- package/src/ui/components/theme-toggle.stories.tsx +1 -1
- package/src/ui/components/tier-card.stories.tsx +1 -1
- package/src/ui/components/toast.stories.tsx +55 -0
- package/src/ui/components/toast.tsx +49 -0
- package/src/ui/components/tv.stories.tsx +1 -1
- package/src/ui/components/watchlist.stories.tsx +1 -1
- package/src/ui/globals.css +14 -2
- package/dist/ui/components/modal/index.d.ts +0 -8
- package/dist/ui/components/modal/index.js +0 -35
- package/dist/ui/components/modal/modal.css +0 -36
- package/src/ui/components/modal/index.stories.tsx +0 -46
- package/src/ui/components/modal/index.tsx +0 -48
- package/src/ui/components/modal/modal.css +0 -36
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import { Button } from './button'
|
|
5
|
+
import { Checkbox } from './checkbox'
|
|
6
|
+
import { Input } from './input'
|
|
7
|
+
import { Label } from './label'
|
|
8
|
+
import { Select, SelectOption } from './select'
|
|
9
|
+
import { Separator } from './separator'
|
|
10
|
+
import { Switch } from './switch'
|
|
11
|
+
|
|
12
|
+
const meta: Meta = {
|
|
13
|
+
title: 'Components/Forms/All Forms'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default meta
|
|
17
|
+
|
|
18
|
+
type Story = StoryObj
|
|
19
|
+
|
|
20
|
+
export const AllFormControls: Story = {
|
|
21
|
+
render: () => {
|
|
22
|
+
function FormDemo() {
|
|
23
|
+
const [name, setName] = useState('Hermes')
|
|
24
|
+
const [email, setEmail] = useState('hermes@nousresearch.com')
|
|
25
|
+
const [provider, setProvider] = useState('anthropic')
|
|
26
|
+
const [logging, setLogging] = useState(true)
|
|
27
|
+
const [telemetry, setTelemetry] = useState(false)
|
|
28
|
+
const [terms, setTerms] = useState(false)
|
|
29
|
+
const [newsletter, setNewsletter] = useState(true)
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className="flex w-full max-w-lg flex-col gap-6">
|
|
33
|
+
<div className="flex flex-col gap-1">
|
|
34
|
+
<h2 className="font-expanded text-sm font-bold tracking-[0.08em] uppercase">
|
|
35
|
+
Form Controls
|
|
36
|
+
</h2>
|
|
37
|
+
|
|
38
|
+
<p className="font-mondwest text-xs text-midground/60">
|
|
39
|
+
All form primitives from the design system.
|
|
40
|
+
</p>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<Separator />
|
|
44
|
+
|
|
45
|
+
<div className="flex flex-col gap-4">
|
|
46
|
+
<Label className="text-midground/50">Text Inputs</Label>
|
|
47
|
+
|
|
48
|
+
<div className="flex flex-col gap-1.5">
|
|
49
|
+
<Label htmlFor="form-name">Name</Label>
|
|
50
|
+
<Input
|
|
51
|
+
id="form-name"
|
|
52
|
+
onChange={e => setName(e.target.value)}
|
|
53
|
+
placeholder="Enter your name"
|
|
54
|
+
value={name}
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div className="flex flex-col gap-1.5">
|
|
59
|
+
<Label htmlFor="form-email">Email</Label>
|
|
60
|
+
<Input
|
|
61
|
+
id="form-email"
|
|
62
|
+
onChange={e => setEmail(e.target.value)}
|
|
63
|
+
placeholder="Enter your email"
|
|
64
|
+
type="email"
|
|
65
|
+
value={email}
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div className="flex flex-col gap-1.5">
|
|
70
|
+
<Label htmlFor="form-disabled">Disabled Input</Label>
|
|
71
|
+
<Input
|
|
72
|
+
disabled
|
|
73
|
+
id="form-disabled"
|
|
74
|
+
placeholder="Cannot edit"
|
|
75
|
+
value="Read-only value"
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<Separator />
|
|
81
|
+
|
|
82
|
+
<div className="flex flex-col gap-4">
|
|
83
|
+
<Label className="text-midground/50">Select</Label>
|
|
84
|
+
|
|
85
|
+
<div className="flex flex-col gap-1.5">
|
|
86
|
+
<Label htmlFor="form-provider">Provider</Label>
|
|
87
|
+
|
|
88
|
+
<Select
|
|
89
|
+
onValueChange={setProvider}
|
|
90
|
+
placeholder="Choose a provider…"
|
|
91
|
+
value={provider}
|
|
92
|
+
>
|
|
93
|
+
<SelectOption value="openai">OpenAI</SelectOption>
|
|
94
|
+
<SelectOption value="anthropic">Anthropic</SelectOption>
|
|
95
|
+
<SelectOption value="google">Google</SelectOption>
|
|
96
|
+
<SelectOption value="mistral">Mistral</SelectOption>
|
|
97
|
+
</Select>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<Separator />
|
|
102
|
+
|
|
103
|
+
<div className="flex flex-col gap-4">
|
|
104
|
+
<Label className="text-midground/50">Switches</Label>
|
|
105
|
+
|
|
106
|
+
<label className="flex items-center justify-between">
|
|
107
|
+
<span className="text-sm">Enable logging</span>
|
|
108
|
+
<Switch checked={logging} onCheckedChange={setLogging} />
|
|
109
|
+
</label>
|
|
110
|
+
|
|
111
|
+
<label className="flex items-center justify-between">
|
|
112
|
+
<span className="text-sm">Send telemetry</span>
|
|
113
|
+
<Switch checked={telemetry} onCheckedChange={setTelemetry} />
|
|
114
|
+
</label>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<Separator />
|
|
118
|
+
|
|
119
|
+
<div className="flex flex-col gap-4">
|
|
120
|
+
<Label className="text-midground/50">Checkboxes</Label>
|
|
121
|
+
|
|
122
|
+
<div className="flex items-center gap-2.5">
|
|
123
|
+
<Checkbox
|
|
124
|
+
checked={terms}
|
|
125
|
+
id="form-terms"
|
|
126
|
+
onCheckedChange={setTerms}
|
|
127
|
+
/>
|
|
128
|
+
|
|
129
|
+
<label className="cursor-pointer text-sm" htmlFor="form-terms">
|
|
130
|
+
Accept terms and conditions
|
|
131
|
+
</label>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<div className="flex items-center gap-2.5">
|
|
135
|
+
<Checkbox
|
|
136
|
+
checked={newsletter}
|
|
137
|
+
id="form-newsletter"
|
|
138
|
+
onCheckedChange={setNewsletter}
|
|
139
|
+
/>
|
|
140
|
+
|
|
141
|
+
<label className="cursor-pointer text-sm" htmlFor="form-newsletter">
|
|
142
|
+
Subscribe to newsletter
|
|
143
|
+
</label>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<Separator />
|
|
148
|
+
|
|
149
|
+
<div className="flex flex-col gap-4">
|
|
150
|
+
<Label className="text-midground/50">Buttons</Label>
|
|
151
|
+
|
|
152
|
+
<div className="flex flex-wrap gap-2">
|
|
153
|
+
<Button>Primary</Button>
|
|
154
|
+
<Button outlined>Outlined</Button>
|
|
155
|
+
<Button invert>Inverted</Button>
|
|
156
|
+
<Button destructive>Destructive</Button>
|
|
157
|
+
<Button disabled>Disabled</Button>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
<Separator />
|
|
162
|
+
|
|
163
|
+
<div className="flex items-center justify-end gap-2">
|
|
164
|
+
<Button outlined>Cancel</Button>
|
|
165
|
+
<Button>Save Changes</Button>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return <FormDemo />
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -8,7 +8,7 @@ import { ImageDistortion } from './image-distortion'
|
|
|
8
8
|
const meta: Meta<typeof ImageDistortion> = {
|
|
9
9
|
args: { active: true, src: fillerBg.src ?? (fillerBg as unknown as string) },
|
|
10
10
|
component: ImageDistortion,
|
|
11
|
-
title: 'Components/ImageDistortion'
|
|
11
|
+
title: 'Components/Effects/ImageDistortion'
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export default meta
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
|
|
3
|
+
import { Input } from './input'
|
|
4
|
+
import { Label } from './label'
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Input> = {
|
|
7
|
+
component: Input,
|
|
8
|
+
title: 'Components/Forms/Input'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default meta
|
|
12
|
+
|
|
13
|
+
type Story = StoryObj<typeof Input>
|
|
14
|
+
|
|
15
|
+
export const Playground: Story = {
|
|
16
|
+
render: () => <Input placeholder="Enter a value…" />
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const Disabled: Story = {
|
|
20
|
+
render: () => <Input disabled placeholder="Disabled" value="locked" />
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const WithLabel: Story = {
|
|
24
|
+
render: () => (
|
|
25
|
+
<div className="grid w-64 gap-1.5">
|
|
26
|
+
<Label htmlFor="demo-input">Model name</Label>
|
|
27
|
+
<Input id="demo-input" placeholder="e.g. gpt-4o" />
|
|
28
|
+
</div>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const NumberInput: Story = {
|
|
33
|
+
render: () => (
|
|
34
|
+
<div className="grid w-40 gap-1.5">
|
|
35
|
+
<Label htmlFor="demo-number">Temperature</Label>
|
|
36
|
+
<Input id="demo-number" type="number" step={0.1} defaultValue={0.7} />
|
|
37
|
+
</div>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { cn } from '../../utils'
|
|
2
|
+
|
|
3
|
+
export function Input({
|
|
4
|
+
className,
|
|
5
|
+
...props
|
|
6
|
+
}: React.InputHTMLAttributes<HTMLInputElement>) {
|
|
7
|
+
return (
|
|
8
|
+
<input
|
|
9
|
+
className={cn(
|
|
10
|
+
'flex h-9 w-full border border-midground/15 bg-background/40 px-3 py-1 font-courier text-sm transition-colors',
|
|
11
|
+
'placeholder:text-midground/50',
|
|
12
|
+
'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-midground/30 focus-visible:border-midground/25',
|
|
13
|
+
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
14
|
+
className
|
|
15
|
+
)}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
|
|
3
|
+
import { Input } from './input'
|
|
4
|
+
import { Label } from './label'
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Label> = {
|
|
7
|
+
component: Label,
|
|
8
|
+
title: 'Components/Forms/Label'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default meta
|
|
12
|
+
|
|
13
|
+
type Story = StoryObj<typeof Label>
|
|
14
|
+
|
|
15
|
+
export const Playground: Story = {
|
|
16
|
+
render: () => <Label>Field label</Label>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const WithInput: Story = {
|
|
20
|
+
render: () => (
|
|
21
|
+
<div className="grid w-64 gap-1.5">
|
|
22
|
+
<Label htmlFor="label-demo">API key</Label>
|
|
23
|
+
<Input id="label-demo" type="password" placeholder="sk-…" />
|
|
24
|
+
</div>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { cn } from '../../utils'
|
|
2
|
+
|
|
3
|
+
export function Label({
|
|
4
|
+
className,
|
|
5
|
+
...props
|
|
6
|
+
}: React.LabelHTMLAttributes<HTMLLabelElement>) {
|
|
7
|
+
return (
|
|
8
|
+
<label
|
|
9
|
+
className={cn(
|
|
10
|
+
'font-mondwest text-xs tracking-[0.1em] uppercase leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
|
11
|
+
className
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
|
|
3
|
+
import { Separator } from './separator'
|
|
4
|
+
import { Small } from './typography/small'
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Separator> = {
|
|
7
|
+
component: Separator,
|
|
8
|
+
title: 'Components/Layout/Separator'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default meta
|
|
12
|
+
|
|
13
|
+
type Story = StoryObj<typeof Separator>
|
|
14
|
+
|
|
15
|
+
export const Horizontal: Story = {
|
|
16
|
+
render: () => (
|
|
17
|
+
<div className="grid w-64 gap-3">
|
|
18
|
+
<Small className="opacity-60 uppercase tracking-wider">Section A</Small>
|
|
19
|
+
<Separator />
|
|
20
|
+
<Small className="opacity-60 uppercase tracking-wider">Section B</Small>
|
|
21
|
+
</div>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const Vertical: Story = {
|
|
26
|
+
render: () => (
|
|
27
|
+
<div className="flex h-8 items-center gap-3">
|
|
28
|
+
<Small className="opacity-60 uppercase tracking-wider">Left</Small>
|
|
29
|
+
<Separator orientation="vertical" />
|
|
30
|
+
<Small className="opacity-60 uppercase tracking-wider">Right</Small>
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { cn } from '../../utils'
|
|
2
|
+
|
|
3
|
+
export function Separator({
|
|
4
|
+
className,
|
|
5
|
+
orientation = 'horizontal',
|
|
6
|
+
...props
|
|
7
|
+
}: SeparatorProps) {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
aria-orientation={orientation}
|
|
11
|
+
role="separator"
|
|
12
|
+
className={cn(
|
|
13
|
+
'shrink-0 bg-midground/15',
|
|
14
|
+
orientation === 'horizontal' ? 'h-px w-full' : 'h-full w-px',
|
|
15
|
+
className
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface SeparatorProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
23
|
+
orientation?: 'horizontal' | 'vertical'
|
|
24
|
+
}
|
|
@@ -42,7 +42,7 @@ const SEQUENCE: TerminalDemoStep[] = [
|
|
|
42
42
|
const meta: Meta<typeof TerminalDemo> = {
|
|
43
43
|
args: { label: 'Hermes', sequence: SEQUENCE },
|
|
44
44
|
component: TerminalDemo,
|
|
45
|
-
title: 'Components/TerminalDemo'
|
|
45
|
+
title: 'Components/Data Display/TerminalDemo'
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
export default meta
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
|
|
3
|
+
import { useToast } from '../../hooks/use-toast'
|
|
4
|
+
import { Button } from './button'
|
|
5
|
+
import { Toast } from './toast'
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof Toast> = {
|
|
8
|
+
component: Toast,
|
|
9
|
+
title: 'Components/Feedback/Toast'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default meta
|
|
13
|
+
|
|
14
|
+
type Story = StoryObj<typeof Toast>
|
|
15
|
+
|
|
16
|
+
export const Success: Story = {
|
|
17
|
+
render: () => {
|
|
18
|
+
function Demo() {
|
|
19
|
+
const { showToast, toast } = useToast()
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<Button onClick={() => showToast('Operation succeeded', 'success')}>
|
|
24
|
+
Show success toast
|
|
25
|
+
</Button>
|
|
26
|
+
<Toast toast={toast} />
|
|
27
|
+
</>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return <Demo />
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const Error: Story = {
|
|
36
|
+
render: () => {
|
|
37
|
+
function Demo() {
|
|
38
|
+
const { showToast, toast } = useToast()
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<>
|
|
42
|
+
<Button
|
|
43
|
+
destructive
|
|
44
|
+
onClick={() => showToast('Something went wrong', 'error')}
|
|
45
|
+
>
|
|
46
|
+
Show error toast
|
|
47
|
+
</Button>
|
|
48
|
+
<Toast toast={toast} />
|
|
49
|
+
</>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return <Demo />
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react'
|
|
4
|
+
import { createPortal } from 'react-dom'
|
|
5
|
+
|
|
6
|
+
import { cn } from '../../utils'
|
|
7
|
+
|
|
8
|
+
export function Toast({ toast }: ToastProps) {
|
|
9
|
+
const [visible, setVisible] = useState(false)
|
|
10
|
+
const [current, setCurrent] = useState(toast)
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (toast) {
|
|
14
|
+
setCurrent(toast)
|
|
15
|
+
setVisible(true)
|
|
16
|
+
} else {
|
|
17
|
+
setVisible(false)
|
|
18
|
+
const timer = setTimeout(() => setCurrent(null), 200)
|
|
19
|
+
return () => clearTimeout(timer)
|
|
20
|
+
}
|
|
21
|
+
}, [toast])
|
|
22
|
+
|
|
23
|
+
if (!current || typeof document === 'undefined') return null
|
|
24
|
+
|
|
25
|
+
return createPortal(
|
|
26
|
+
<div
|
|
27
|
+
aria-live="polite"
|
|
28
|
+
className={cn(
|
|
29
|
+
'fixed top-16 right-4 z-50 border px-4 py-2.5 font-courier text-xs tracking-wider uppercase backdrop-blur-sm',
|
|
30
|
+
current.type === 'success'
|
|
31
|
+
? 'bg-success/15 text-success border-success/30'
|
|
32
|
+
: 'bg-destructive/15 text-destructive border-destructive/30'
|
|
33
|
+
)}
|
|
34
|
+
role="status"
|
|
35
|
+
style={{
|
|
36
|
+
animation: visible
|
|
37
|
+
? 'toast-in 200ms ease-out forwards'
|
|
38
|
+
: 'toast-out 200ms ease-in forwards'
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
{current.message}
|
|
42
|
+
</div>,
|
|
43
|
+
document.body
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface ToastProps {
|
|
48
|
+
toast: { message: string; type: 'error' | 'success' } | null
|
|
49
|
+
}
|
package/src/ui/globals.css
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
@source ".";
|
|
2
2
|
@import './components/fit-text/fit-text.css' layer(components);
|
|
3
3
|
@import './components/grid/grid.css' layer(components);
|
|
4
|
-
@import './components/modal/modal.css' layer(components);
|
|
5
|
-
|
|
6
4
|
@view-transition {
|
|
7
5
|
navigation: auto;
|
|
8
6
|
}
|
|
@@ -253,6 +251,20 @@
|
|
|
253
251
|
text-underline-position: from-font;
|
|
254
252
|
}
|
|
255
253
|
|
|
254
|
+
@keyframes toast-in {
|
|
255
|
+
from {
|
|
256
|
+
opacity: 0;
|
|
257
|
+
transform: translateX(1rem);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@keyframes toast-out {
|
|
262
|
+
to {
|
|
263
|
+
opacity: 0;
|
|
264
|
+
transform: translateX(1rem);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
256
268
|
@keyframes gradient-stroke {
|
|
257
269
|
0% {
|
|
258
270
|
background-position: 15% 15%;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export declare function Modal({ children, className, id, trigger, ...props }: ModalProps): import("react").JSX.Element;
|
|
2
|
-
interface ModalProps extends Omit<React.ComponentProps<'dialog'>, 'open'> {
|
|
3
|
-
trigger: (controls: {
|
|
4
|
-
close: () => void;
|
|
5
|
-
open: () => void;
|
|
6
|
-
}) => React.ReactNode;
|
|
7
|
-
}
|
|
8
|
-
export {};
|