@nous-research/ui 0.15.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 +266 -0
- package/README.md +24 -4
- package/dist/fonts.js +1 -0
- package/dist/hooks/use-below-breakpoint.d.ts +2 -0
- package/dist/hooks/use-below-breakpoint.js +17 -0
- package/dist/hooks/use-capped-frame.js +1 -0
- package/dist/hooks/use-confirm-delete.d.ts +10 -0
- package/dist/hooks/use-confirm-delete.js +35 -0
- package/dist/hooks/use-css-var-dims.js +1 -0
- package/dist/hooks/use-gpu-tier.js +1 -0
- package/dist/hooks/use-render-loop.js +1 -0
- package/dist/hooks/use-smooth-controls.js +1 -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 -1
- package/dist/ui/basic-page.js +1 -0
- package/dist/ui/components/animated-count.js +1 -0
- package/dist/ui/components/ascii.js +1 -0
- package/dist/ui/components/badge.js +2 -1
- package/dist/ui/components/badges/nous-girl.js +1 -0
- package/dist/ui/components/blend-mode.js +1 -0
- package/dist/ui/components/blink.js +1 -0
- package/dist/ui/components/bottom-sheet.d.ts +15 -0
- package/dist/ui/components/bottom-sheet.js +192 -0
- package/dist/ui/components/button.js +2 -1
- 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 -1
- package/dist/ui/components/command-block.js +4 -3
- package/dist/ui/components/confirm-dialog.d.ts +13 -0
- package/dist/ui/components/confirm-dialog.js +113 -0
- package/dist/ui/components/cursor.js +1 -0
- package/dist/ui/components/dialog.d.ts +15 -0
- package/dist/ui/components/dialog.js +171 -0
- package/dist/ui/components/dropdown-menu.js +1 -0
- package/dist/ui/components/fit-text/index.js +1 -0
- package/dist/ui/components/graphs/bar-chart.js +1 -0
- package/dist/ui/components/graphs/index.js +1 -0
- package/dist/ui/components/graphs/line-chart.js +1 -0
- package/dist/ui/components/graphs/utils.js +1 -0
- package/dist/ui/components/grid/index.js +1 -0
- package/dist/ui/components/hover-bg.js +1 -0
- package/dist/ui/components/icons/arrow.js +1 -0
- package/dist/ui/components/icons/check.js +1 -0
- package/dist/ui/components/icons/chevron.js +1 -0
- package/dist/ui/components/icons/discord.js +1 -0
- package/dist/ui/components/icons/eye.js +1 -0
- package/dist/ui/components/icons/gear.js +1 -0
- package/dist/ui/components/icons/github.js +1 -0
- package/dist/ui/components/icons/hamburger.js +1 -0
- package/dist/ui/components/icons/heart.js +1 -0
- package/dist/ui/components/icons/index.js +1 -0
- package/dist/ui/components/icons/link.js +1 -0
- package/dist/ui/components/icons/minus.js +1 -0
- package/dist/ui/components/icons/search.js +1 -0
- package/dist/ui/components/image-distortion.js +1 -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/leva-client.js +1 -0
- package/dist/ui/components/list-item.js +3 -2
- package/dist/ui/components/overlays/blend-modes.js +1 -0
- package/dist/ui/components/overlays/glitch.js +1 -0
- package/dist/ui/components/overlays/greys.js +1 -0
- package/dist/ui/components/overlays/index.js +1 -0
- package/dist/ui/components/overlays/lens-layers.js +1 -0
- package/dist/ui/components/overlays/lens.js +1 -0
- package/dist/ui/components/overlays/noise.js +1 -0
- package/dist/ui/components/overlays/vignette.js +1 -0
- package/dist/ui/components/poster.js +1 -0
- package/dist/ui/components/progress.js +1 -0
- package/dist/ui/components/scene-canvas.js +1 -0
- package/dist/ui/components/scramble.js +1 -0
- package/dist/ui/components/segmented.js +5 -4
- package/dist/ui/components/select.js +1 -0
- package/dist/ui/components/selection-switcher.js +1 -0
- package/dist/ui/components/separator.d.ts +5 -0
- package/dist/ui/components/separator.js +22 -0
- package/dist/ui/components/shader.js +1 -0
- package/dist/ui/components/socials.js +1 -0
- package/dist/ui/components/spinner.js +1 -0
- package/dist/ui/components/stats.js +2 -1
- package/dist/ui/components/switch.js +1 -0
- package/dist/ui/components/tabs.js +4 -3
- package/dist/ui/components/terminal-demo.js +2 -1
- package/dist/ui/components/theme-toggle.js +1 -0
- package/dist/ui/components/tier-card.js +2 -1
- package/dist/ui/components/toast.d.ts +8 -0
- package/dist/ui/components/toast.js +39 -0
- package/dist/ui/components/tv.js +1 -0
- package/dist/ui/components/typography/h1.js +1 -0
- package/dist/ui/components/typography/h2.js +1 -0
- package/dist/ui/components/typography/index.js +1 -0
- package/dist/ui/components/typography/legend.js +1 -0
- package/dist/ui/components/typography/small.js +1 -0
- package/dist/ui/components/watchlist.js +2 -1
- package/dist/ui/footer.js +1 -0
- package/dist/ui/globals.css +47 -3
- package/dist/ui/header.js +1 -0
- package/dist/ui/layout-wrapper.js +2 -1
- package/dist/utils/color.js +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/poly.js +1 -0
- package/package.json +5 -3
- package/src/assets/filler-bg0.webp +0 -0
- package/src/assets.d.ts +38 -0
- package/src/fonts/Collapse-Bold.woff2 +0 -0
- package/src/fonts/Collapse-BoldItalic.woff2 +0 -0
- package/src/fonts/Collapse-Italic.woff2 +0 -0
- package/src/fonts/Collapse-Light.woff2 +0 -0
- package/src/fonts/Collapse-LightItalic.woff2 +0 -0
- package/src/fonts/Collapse-Regular.woff2 +0 -0
- package/src/fonts/Collapse-Thin.woff2 +0 -0
- package/src/fonts/Collapse-ThinItalic.woff2 +0 -0
- package/src/fonts/Mondwest-Regular.woff2 +0 -0
- package/src/fonts/Neuebit-Bold.woff2 +0 -0
- package/src/fonts/RulesCompressed-Medium.woff2 +0 -0
- package/src/fonts/RulesCompressed-Regular.woff2 +0 -0
- package/src/fonts/RulesExpanded-Bold.woff2 +0 -0
- package/src/fonts/RulesExpanded-Regular.woff2 +0 -0
- package/src/fonts.ts +6 -0
- package/src/hooks/use-below-breakpoint.ts +21 -0
- package/src/hooks/use-capped-frame.ts +18 -0
- package/src/hooks/use-confirm-delete.ts +43 -0
- package/src/hooks/use-css-var-dims.ts +39 -0
- package/src/hooks/use-gpu-tier.ts +165 -0
- package/src/hooks/use-render-loop.ts +121 -0
- package/src/hooks/use-smooth-controls.ts +318 -0
- package/src/hooks/use-toast.ts +29 -0
- package/src/index.ts +130 -0
- package/src/ui/basic-page.tsx +34 -0
- package/src/ui/build.css +4 -0
- package/src/ui/components/animated-count.stories.tsx +67 -0
- package/src/ui/components/animated-count.tsx +168 -0
- package/src/ui/components/ascii.stories.tsx +30 -0
- package/src/ui/components/ascii.tsx +110 -0
- package/src/ui/components/badge.stories.tsx +31 -0
- package/src/ui/components/badge.tsx +60 -0
- package/src/ui/components/badges/nous-girl.tsx +52 -0
- package/src/ui/components/blend-mode.stories.tsx +33 -0
- package/src/ui/components/blend-mode.tsx +129 -0
- package/src/ui/components/blink.stories.tsx +32 -0
- package/src/ui/components/blink.tsx +21 -0
- 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 +68 -0
- package/src/ui/components/button.tsx +170 -0
- package/src/ui/components/card.stories.tsx +63 -0
- package/src/ui/components/card.tsx +85 -0
- package/src/ui/components/checkbox.stories.tsx +113 -0
- package/src/ui/components/checkbox.tsx +36 -0
- package/src/ui/components/command-block.stories.tsx +52 -0
- package/src/ui/components/command-block.tsx +86 -0
- package/src/ui/components/confirm-dialog.stories.tsx +91 -0
- package/src/ui/components/confirm-dialog.tsx +130 -0
- package/src/ui/components/cursor.tsx +115 -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 +52 -0
- package/src/ui/components/dropdown-menu.tsx +117 -0
- package/src/ui/components/fit-text/fit-text.css +42 -0
- package/src/ui/components/fit-text/index.stories.tsx +33 -0
- package/src/ui/components/fit-text/index.tsx +45 -0
- package/src/ui/components/forms.stories.tsx +173 -0
- package/src/ui/components/graphs/bar-chart.tsx +153 -0
- package/src/ui/components/graphs/index.stories.tsx +64 -0
- package/src/ui/components/graphs/index.tsx +4 -0
- package/src/ui/components/graphs/line-chart.tsx +213 -0
- package/src/ui/components/graphs/utils.tsx +265 -0
- package/src/ui/components/grid/grid.css +79 -0
- package/src/ui/components/grid/index.tsx +19 -0
- package/src/ui/components/hover-bg.stories.tsx +29 -0
- package/src/ui/components/hover-bg.tsx +15 -0
- package/src/ui/components/icons/arrow.tsx +42 -0
- package/src/ui/components/icons/check.tsx +14 -0
- package/src/ui/components/icons/chevron.tsx +45 -0
- package/src/ui/components/icons/discord.tsx +16 -0
- package/src/ui/components/icons/eye.tsx +12 -0
- package/src/ui/components/icons/gear.tsx +51 -0
- package/src/ui/components/icons/github.tsx +16 -0
- package/src/ui/components/icons/hamburger.tsx +52 -0
- package/src/ui/components/icons/heart.tsx +12 -0
- package/src/ui/components/icons/index.ts +12 -0
- package/src/ui/components/icons/link.tsx +14 -0
- package/src/ui/components/icons/minus.tsx +14 -0
- package/src/ui/components/icons/search.tsx +28 -0
- package/src/ui/components/image-distortion.stories.tsx +120 -0
- package/src/ui/components/image-distortion.tsx +498 -0
- 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/leva-client.tsx +14 -0
- package/src/ui/components/list-item.stories.tsx +83 -0
- package/src/ui/components/list-item.tsx +37 -0
- package/src/ui/components/overlays/blend-modes.ts +13 -0
- package/src/ui/components/overlays/glitch.tsx +243 -0
- package/src/ui/components/overlays/greys.tsx +386 -0
- package/src/ui/components/overlays/index.tsx +47 -0
- package/src/ui/components/overlays/lens-layers.tsx +119 -0
- package/src/ui/components/overlays/lens.ts +91 -0
- package/src/ui/components/overlays/noise.tsx +174 -0
- package/src/ui/components/overlays/vignette.tsx +60 -0
- package/src/ui/components/poster.stories.tsx +513 -0
- package/src/ui/components/poster.tsx +411 -0
- package/src/ui/components/progress.stories.tsx +48 -0
- package/src/ui/components/progress.tsx +56 -0
- package/src/ui/components/scene-canvas.tsx +254 -0
- package/src/ui/components/scramble.stories.tsx +49 -0
- package/src/ui/components/scramble.tsx +95 -0
- package/src/ui/components/segmented.stories.tsx +101 -0
- package/src/ui/components/segmented.tsx +81 -0
- package/src/ui/components/select.stories.tsx +88 -0
- package/src/ui/components/select.tsx +267 -0
- package/src/ui/components/selection-switcher.tsx +44 -0
- package/src/ui/components/separator.stories.tsx +33 -0
- package/src/ui/components/separator.tsx +24 -0
- package/src/ui/components/shader.tsx +83 -0
- package/src/ui/components/socials.tsx +42 -0
- package/src/ui/components/spinner.stories.tsx +101 -0
- package/src/ui/components/spinner.tsx +60 -0
- package/src/ui/components/stats.stories.tsx +24 -0
- package/src/ui/components/stats.tsx +53 -0
- package/src/ui/components/switch.stories.tsx +77 -0
- package/src/ui/components/switch.tsx +48 -0
- package/src/ui/components/tabs.stories.tsx +101 -0
- package/src/ui/components/tabs.tsx +66 -0
- package/src/ui/components/terminal-demo.stories.tsx +67 -0
- package/src/ui/components/terminal-demo.tsx +189 -0
- package/src/ui/components/theme-toggle.stories.tsx +47 -0
- package/src/ui/components/theme-toggle.tsx +66 -0
- package/src/ui/components/tier-card.stories.tsx +217 -0
- package/src/ui/components/tier-card.tsx +190 -0
- package/src/ui/components/toast.stories.tsx +55 -0
- package/src/ui/components/toast.tsx +49 -0
- package/src/ui/components/tv.stories.tsx +37 -0
- package/src/ui/components/tv.tsx +257 -0
- package/src/ui/components/typography/h1.tsx +18 -0
- package/src/ui/components/typography/h2.tsx +18 -0
- package/src/ui/components/typography/index.tsx +54 -0
- package/src/ui/components/typography/legend.tsx +24 -0
- package/src/ui/components/typography/small.tsx +11 -0
- package/src/ui/components/watchlist.stories.tsx +33 -0
- package/src/ui/components/watchlist.tsx +105 -0
- package/src/ui/fonts.css +63 -0
- package/src/ui/footer.tsx +111 -0
- package/src/ui/globals.css +395 -0
- package/src/ui/header.tsx +398 -0
- package/src/ui/layout-wrapper.tsx +11 -0
- package/src/utils/color.ts +21 -0
- package/src/utils/index.ts +62 -0
- package/src/utils/poly.ts +26 -0
- package/dist/ui/components/modal/index.d.ts +0 -8
- package/dist/ui/components/modal/index.js +0 -34
- package/dist/ui/components/modal/modal.css +0 -36
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import { Button } from './button'
|
|
5
|
+
import {
|
|
6
|
+
Dialog,
|
|
7
|
+
DialogClose,
|
|
8
|
+
DialogContent,
|
|
9
|
+
DialogDescription,
|
|
10
|
+
DialogFooter,
|
|
11
|
+
DialogHeader,
|
|
12
|
+
DialogTitle,
|
|
13
|
+
DialogTrigger
|
|
14
|
+
} from './dialog'
|
|
15
|
+
import { Input } from './input'
|
|
16
|
+
import { Label } from './label'
|
|
17
|
+
|
|
18
|
+
const meta: Meta<typeof Dialog> = {
|
|
19
|
+
component: Dialog,
|
|
20
|
+
title: 'Components/Overlays/Dialog'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default meta
|
|
24
|
+
|
|
25
|
+
type Story = StoryObj<typeof Dialog>
|
|
26
|
+
|
|
27
|
+
export const Default: Story = {
|
|
28
|
+
render: () => (
|
|
29
|
+
<Dialog>
|
|
30
|
+
<DialogTrigger asChild>
|
|
31
|
+
<Button>Open Dialog</Button>
|
|
32
|
+
</DialogTrigger>
|
|
33
|
+
|
|
34
|
+
<DialogContent>
|
|
35
|
+
<DialogHeader>
|
|
36
|
+
<DialogTitle>Dialog Title</DialogTitle>
|
|
37
|
+
<DialogDescription>
|
|
38
|
+
A description of the dialog content and its purpose.
|
|
39
|
+
</DialogDescription>
|
|
40
|
+
</DialogHeader>
|
|
41
|
+
|
|
42
|
+
<div className="p-4">
|
|
43
|
+
<p className="font-courier text-sm text-midground/80">
|
|
44
|
+
This is a general-purpose dialog built on Radix UI primitives. It
|
|
45
|
+
handles focus trapping, ESC to close, and backdrop click
|
|
46
|
+
automatically.
|
|
47
|
+
</p>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<DialogFooter>
|
|
51
|
+
<DialogClose asChild>
|
|
52
|
+
<Button outlined>Close</Button>
|
|
53
|
+
</DialogClose>
|
|
54
|
+
</DialogFooter>
|
|
55
|
+
</DialogContent>
|
|
56
|
+
</Dialog>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const Controlled: Story = {
|
|
61
|
+
render: () => {
|
|
62
|
+
function Demo() {
|
|
63
|
+
const [open, setOpen] = useState(false)
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<>
|
|
67
|
+
<Button onClick={() => setOpen(true)}>Controlled Open</Button>
|
|
68
|
+
|
|
69
|
+
<Dialog onOpenChange={setOpen} open={open}>
|
|
70
|
+
<DialogContent>
|
|
71
|
+
<DialogHeader>
|
|
72
|
+
<DialogTitle>Controlled Dialog</DialogTitle>
|
|
73
|
+
<DialogDescription>
|
|
74
|
+
This dialog is controlled via external state.
|
|
75
|
+
</DialogDescription>
|
|
76
|
+
</DialogHeader>
|
|
77
|
+
|
|
78
|
+
<div className="p-4">
|
|
79
|
+
<p className="font-courier text-sm text-midground/80">
|
|
80
|
+
Open state is managed by the parent component. Useful when
|
|
81
|
+
you need to open the dialog programmatically.
|
|
82
|
+
</p>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<DialogFooter>
|
|
86
|
+
<Button onClick={() => setOpen(false)} outlined>
|
|
87
|
+
Cancel
|
|
88
|
+
</Button>
|
|
89
|
+
|
|
90
|
+
<Button onClick={() => setOpen(false)}>
|
|
91
|
+
Save
|
|
92
|
+
</Button>
|
|
93
|
+
</DialogFooter>
|
|
94
|
+
</DialogContent>
|
|
95
|
+
</Dialog>
|
|
96
|
+
</>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return <Demo />
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const WithForm: Story = {
|
|
105
|
+
render: () => (
|
|
106
|
+
<Dialog>
|
|
107
|
+
<DialogTrigger asChild>
|
|
108
|
+
<Button>Edit Profile</Button>
|
|
109
|
+
</DialogTrigger>
|
|
110
|
+
|
|
111
|
+
<DialogContent>
|
|
112
|
+
<DialogHeader>
|
|
113
|
+
<DialogTitle>Edit Profile</DialogTitle>
|
|
114
|
+
<DialogDescription>
|
|
115
|
+
Make changes to your profile. Click save when you are done.
|
|
116
|
+
</DialogDescription>
|
|
117
|
+
</DialogHeader>
|
|
118
|
+
|
|
119
|
+
<div className="flex flex-col gap-4 p-4">
|
|
120
|
+
<div className="flex flex-col gap-1.5">
|
|
121
|
+
<Label htmlFor="name">Name</Label>
|
|
122
|
+
<Input defaultValue="Hermes" id="name" />
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<div className="flex flex-col gap-1.5">
|
|
126
|
+
<Label htmlFor="email">Email</Label>
|
|
127
|
+
<Input defaultValue="hermes@nousresearch.com" id="email" type="email" />
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<DialogFooter>
|
|
132
|
+
<DialogClose asChild>
|
|
133
|
+
<Button outlined>Cancel</Button>
|
|
134
|
+
</DialogClose>
|
|
135
|
+
|
|
136
|
+
<DialogClose asChild>
|
|
137
|
+
<Button>Save Changes</Button>
|
|
138
|
+
</DialogClose>
|
|
139
|
+
</DialogFooter>
|
|
140
|
+
</DialogContent>
|
|
141
|
+
</Dialog>
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const NoCloseButton: Story = {
|
|
146
|
+
render: () => (
|
|
147
|
+
<Dialog>
|
|
148
|
+
<DialogTrigger asChild>
|
|
149
|
+
<Button>Without Close Button</Button>
|
|
150
|
+
</DialogTrigger>
|
|
151
|
+
|
|
152
|
+
<DialogContent showCloseButton={false}>
|
|
153
|
+
<DialogHeader>
|
|
154
|
+
<DialogTitle>Minimal Dialog</DialogTitle>
|
|
155
|
+
<DialogDescription>
|
|
156
|
+
This dialog hides the X close button. Users can still close it
|
|
157
|
+
by pressing ESC or clicking the backdrop.
|
|
158
|
+
</DialogDescription>
|
|
159
|
+
</DialogHeader>
|
|
160
|
+
|
|
161
|
+
<DialogFooter>
|
|
162
|
+
<DialogClose asChild>
|
|
163
|
+
<Button>Got it</Button>
|
|
164
|
+
</DialogClose>
|
|
165
|
+
</DialogFooter>
|
|
166
|
+
</DialogContent>
|
|
167
|
+
</Dialog>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { Dialog as DialogPrimitive } from 'radix-ui'
|
|
5
|
+
|
|
6
|
+
import { cn } from '../../utils'
|
|
7
|
+
|
|
8
|
+
function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
|
9
|
+
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
|
13
|
+
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
|
17
|
+
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
|
21
|
+
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function DialogOverlay({
|
|
25
|
+
className,
|
|
26
|
+
...props
|
|
27
|
+
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
|
28
|
+
return (
|
|
29
|
+
<DialogPrimitive.Overlay
|
|
30
|
+
className={cn(
|
|
31
|
+
'fixed inset-0 z-50',
|
|
32
|
+
'bg-black/60 backdrop-blur-sm',
|
|
33
|
+
'data-[state=open]:animate-in data-[state=open]:fade-in-0',
|
|
34
|
+
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0',
|
|
35
|
+
className
|
|
36
|
+
)}
|
|
37
|
+
data-slot="dialog-overlay"
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function DialogContent({
|
|
44
|
+
className,
|
|
45
|
+
children,
|
|
46
|
+
showCloseButton = true,
|
|
47
|
+
...props
|
|
48
|
+
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
49
|
+
showCloseButton?: boolean
|
|
50
|
+
}) {
|
|
51
|
+
return (
|
|
52
|
+
<DialogPortal>
|
|
53
|
+
<DialogOverlay />
|
|
54
|
+
|
|
55
|
+
<DialogPrimitive.Content
|
|
56
|
+
className={cn(
|
|
57
|
+
'fixed top-1/2 left-1/2 z-50 -translate-x-1/2 -translate-y-1/2',
|
|
58
|
+
'grid w-full max-w-md gap-0',
|
|
59
|
+
'border border-midground/15 bg-background-base text-foreground-base shadow-lg outline-none',
|
|
60
|
+
'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
|
61
|
+
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
|
|
62
|
+
'duration-150',
|
|
63
|
+
className
|
|
64
|
+
)}
|
|
65
|
+
data-slot="dialog-content"
|
|
66
|
+
{...props}
|
|
67
|
+
>
|
|
68
|
+
{children}
|
|
69
|
+
|
|
70
|
+
{showCloseButton && (
|
|
71
|
+
<DialogPrimitive.Close
|
|
72
|
+
className={cn(
|
|
73
|
+
'absolute top-3 right-3',
|
|
74
|
+
'flex h-6 w-6 items-center justify-center',
|
|
75
|
+
'text-midground/50 transition-colors hover:text-midground',
|
|
76
|
+
'focus:outline-none focus-visible:ring-1 focus-visible:ring-midground/30',
|
|
77
|
+
'disabled:pointer-events-none'
|
|
78
|
+
)}
|
|
79
|
+
data-slot="dialog-close"
|
|
80
|
+
>
|
|
81
|
+
<XIcon className="h-3.5 w-3.5" />
|
|
82
|
+
<span className="sr-only">Close</span>
|
|
83
|
+
</DialogPrimitive.Close>
|
|
84
|
+
)}
|
|
85
|
+
</DialogPrimitive.Content>
|
|
86
|
+
</DialogPortal>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
91
|
+
return (
|
|
92
|
+
<div
|
|
93
|
+
className={cn(
|
|
94
|
+
'flex flex-col gap-1 p-4 border-b border-midground/15',
|
|
95
|
+
className
|
|
96
|
+
)}
|
|
97
|
+
data-slot="dialog-header"
|
|
98
|
+
{...props}
|
|
99
|
+
/>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function DialogFooter({ className, ...props }: React.ComponentProps<'div'>) {
|
|
104
|
+
return (
|
|
105
|
+
<div
|
|
106
|
+
className={cn(
|
|
107
|
+
'flex items-center justify-end gap-2 p-3',
|
|
108
|
+
className
|
|
109
|
+
)}
|
|
110
|
+
data-slot="dialog-footer"
|
|
111
|
+
{...props}
|
|
112
|
+
/>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function DialogTitle({
|
|
117
|
+
className,
|
|
118
|
+
...props
|
|
119
|
+
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
|
120
|
+
return (
|
|
121
|
+
<DialogPrimitive.Title
|
|
122
|
+
className={cn(
|
|
123
|
+
'font-expanded text-sm font-bold tracking-[0.08em] uppercase',
|
|
124
|
+
className
|
|
125
|
+
)}
|
|
126
|
+
data-slot="dialog-title"
|
|
127
|
+
{...props}
|
|
128
|
+
/>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function DialogDescription({
|
|
133
|
+
className,
|
|
134
|
+
...props
|
|
135
|
+
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
|
136
|
+
return (
|
|
137
|
+
<DialogPrimitive.Description
|
|
138
|
+
className={cn(
|
|
139
|
+
'font-mondwest text-xs text-midground/60 leading-relaxed',
|
|
140
|
+
className
|
|
141
|
+
)}
|
|
142
|
+
data-slot="dialog-description"
|
|
143
|
+
{...props}
|
|
144
|
+
/>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function XIcon({ className }: { className?: string }) {
|
|
149
|
+
return (
|
|
150
|
+
<svg
|
|
151
|
+
aria-hidden
|
|
152
|
+
className={className}
|
|
153
|
+
fill="none"
|
|
154
|
+
stroke="currentColor"
|
|
155
|
+
strokeLinecap="round"
|
|
156
|
+
strokeLinejoin="round"
|
|
157
|
+
strokeWidth={2}
|
|
158
|
+
viewBox="0 0 24 24"
|
|
159
|
+
>
|
|
160
|
+
<line x1="18" x2="6" y1="6" y2="18" />
|
|
161
|
+
<line x1="6" x2="18" y1="6" y2="18" />
|
|
162
|
+
</svg>
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export {
|
|
167
|
+
Dialog,
|
|
168
|
+
DialogClose,
|
|
169
|
+
DialogContent,
|
|
170
|
+
DialogDescription,
|
|
171
|
+
DialogFooter,
|
|
172
|
+
DialogHeader,
|
|
173
|
+
DialogOverlay,
|
|
174
|
+
DialogPortal,
|
|
175
|
+
DialogTitle,
|
|
176
|
+
DialogTrigger
|
|
177
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import { DropdownMenu } from './dropdown-menu'
|
|
5
|
+
import { Small } from './typography/small'
|
|
6
|
+
|
|
7
|
+
const OPTIONS = [
|
|
8
|
+
{ label: 'Option A', value: 'a' as const },
|
|
9
|
+
{ label: 'Option B', value: 'b' as const },
|
|
10
|
+
{ label: 'Option C', value: 'c' as const }
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
function Demo({ direction }: { direction: 'down' | 'left' | 'right' | 'up' }) {
|
|
14
|
+
const [value, setValue] = useState<'a' | 'b' | 'c'>('a')
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<DropdownMenu
|
|
18
|
+
direction={direction}
|
|
19
|
+
onChange={setValue}
|
|
20
|
+
options={OPTIONS}
|
|
21
|
+
value={value}
|
|
22
|
+
/>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const meta: Meta<typeof DropdownMenu> = {
|
|
27
|
+
component: DropdownMenu,
|
|
28
|
+
title: 'Components/Overlays/DropdownMenu'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default meta
|
|
32
|
+
|
|
33
|
+
type Story = StoryObj<typeof DropdownMenu>
|
|
34
|
+
|
|
35
|
+
export const Down: Story = { render: () => <Demo direction="down" /> }
|
|
36
|
+
export const Up: Story = { render: () => <Demo direction="up" /> }
|
|
37
|
+
export const Right: Story = { render: () => <Demo direction="right" /> }
|
|
38
|
+
export const Left: Story = { render: () => <Demo direction="left" /> }
|
|
39
|
+
|
|
40
|
+
export const AllDirections: Story = {
|
|
41
|
+
render: () => (
|
|
42
|
+
<div className="flex gap-10">
|
|
43
|
+
{(['down', 'up', 'right', 'left'] as const).map(direction => (
|
|
44
|
+
<div className="flex flex-col gap-1" key={direction}>
|
|
45
|
+
<Small className="capitalize opacity-40">{direction}</Small>
|
|
46
|
+
|
|
47
|
+
<Demo direction={direction} />
|
|
48
|
+
</div>
|
|
49
|
+
))}
|
|
50
|
+
</div>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useId, useRef, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
import { cn } from '../../utils'
|
|
6
|
+
|
|
7
|
+
const font = 'font-mondwest text-[.9375rem] leading-[1.4] tracking-[0.1875rem]'
|
|
8
|
+
|
|
9
|
+
type Direction = 'down' | 'up' | 'left' | 'right'
|
|
10
|
+
|
|
11
|
+
type AnchorStyle = React.CSSProperties & Record<string, string | number>
|
|
12
|
+
|
|
13
|
+
export function DropdownMenu<T extends string>({
|
|
14
|
+
className,
|
|
15
|
+
direction = 'down',
|
|
16
|
+
onChange,
|
|
17
|
+
options,
|
|
18
|
+
value
|
|
19
|
+
}: {
|
|
20
|
+
className?: string
|
|
21
|
+
direction?: Direction
|
|
22
|
+
onChange: (value: T) => void
|
|
23
|
+
options: { label: string; value: T }[]
|
|
24
|
+
value: T
|
|
25
|
+
}) {
|
|
26
|
+
const id = useId()
|
|
27
|
+
const [open, setOpen] = useState(false)
|
|
28
|
+
const ref = useRef<HTMLSpanElement>(null)
|
|
29
|
+
|
|
30
|
+
const anchor = `--dropdown-${id.replace(/:/g, '')}`
|
|
31
|
+
|
|
32
|
+
const panelStyle: AnchorStyle = {
|
|
33
|
+
position: 'fixed',
|
|
34
|
+
positionAnchor: anchor,
|
|
35
|
+
positionTryFallbacks:
|
|
36
|
+
direction === 'left' || direction === 'right'
|
|
37
|
+
? 'flip-inline, flip-block'
|
|
38
|
+
: 'flip-block, flip-inline',
|
|
39
|
+
...(direction === 'up' && {
|
|
40
|
+
left: 'calc(anchor(left) - 0.5rem)',
|
|
41
|
+
top: 'calc(anchor(top) + 1rem)',
|
|
42
|
+
transform: 'translateY(-100%)'
|
|
43
|
+
}),
|
|
44
|
+
...(direction === 'right' && {
|
|
45
|
+
left: 'calc(anchor(right))',
|
|
46
|
+
top: 'calc(anchor(top) - 0.5rem)'
|
|
47
|
+
}),
|
|
48
|
+
...(direction === 'left' && {
|
|
49
|
+
left: 'calc(anchor(left) - 1px)',
|
|
50
|
+
top: 'calc(anchor(top) - 0.5rem)',
|
|
51
|
+
transform: 'translateX(-100%)'
|
|
52
|
+
}),
|
|
53
|
+
...(direction === 'down' && {
|
|
54
|
+
left: 'calc(anchor(left) - 0.5rem)',
|
|
55
|
+
top: 'calc(anchor(top) - 0.5rem)'
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (!open) {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const ac = new AbortController()
|
|
65
|
+
document.addEventListener(
|
|
66
|
+
'mousedown',
|
|
67
|
+
e => {
|
|
68
|
+
if (!ref.current?.contains(e.target as Node)) {
|
|
69
|
+
setOpen(false)
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{ signal: ac.signal }
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return () => ac.abort()
|
|
76
|
+
}, [open])
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<span
|
|
80
|
+
className={cn('relative inline-block align-top', className)}
|
|
81
|
+
ref={ref}
|
|
82
|
+
>
|
|
83
|
+
<span
|
|
84
|
+
className={cn(font, 'inline-block cursor-pointer hover:underline')}
|
|
85
|
+
onClick={() => setOpen(!open)}
|
|
86
|
+
style={{ anchorName: anchor } as AnchorStyle}
|
|
87
|
+
>
|
|
88
|
+
{options.find(o => o.value === value)?.label ?? value}{' '}
|
|
89
|
+
{open ? '↑' : '↓'}
|
|
90
|
+
</span>
|
|
91
|
+
|
|
92
|
+
{open && (
|
|
93
|
+
<div
|
|
94
|
+
className="bg-background-base z-50 flex flex-col"
|
|
95
|
+
style={panelStyle}
|
|
96
|
+
>
|
|
97
|
+
{options.map(o => (
|
|
98
|
+
<span
|
|
99
|
+
className={cn(
|
|
100
|
+
font,
|
|
101
|
+
'block cursor-pointer p-2 whitespace-nowrap',
|
|
102
|
+
o.value === value ? 'underline' : 'hover:bg-midground/10'
|
|
103
|
+
)}
|
|
104
|
+
key={o.value}
|
|
105
|
+
onClick={() => {
|
|
106
|
+
onChange(o.value)
|
|
107
|
+
setOpen(false)
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
{o.label}
|
|
111
|
+
</span>
|
|
112
|
+
))}
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
</span>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
.fit-text {
|
|
2
|
+
--fit-captured-length: initial;
|
|
3
|
+
--fit-support-sentinel: var(--fit-captured-length, 9999px);
|
|
4
|
+
|
|
5
|
+
display: flex;
|
|
6
|
+
container-type: inline-size;
|
|
7
|
+
|
|
8
|
+
> [aria-hidden] {
|
|
9
|
+
visibility: hidden;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
> :not([aria-hidden]) {
|
|
13
|
+
flex-grow: 1;
|
|
14
|
+
container-type: inline-size;
|
|
15
|
+
|
|
16
|
+
--fit-captured-length: 100cqi;
|
|
17
|
+
--fit-available-space: var(--fit-captured-length);
|
|
18
|
+
|
|
19
|
+
> * {
|
|
20
|
+
--fit-support-sentinel: inherit;
|
|
21
|
+
--fit-captured-length: 100cqi;
|
|
22
|
+
--fit-ratio: tan(
|
|
23
|
+
atan2(
|
|
24
|
+
var(--fit-available-space),
|
|
25
|
+
var(--fit-available-space) - var(--fit-captured-length)
|
|
26
|
+
)
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
display: block;
|
|
30
|
+
inline-size: var(--fit-available-space);
|
|
31
|
+
font-size: clamp(
|
|
32
|
+
var(--fit-min, 1em),
|
|
33
|
+
1em * var(--fit-ratio),
|
|
34
|
+
var(--fit-max, infinity * 1px) - var(--fit-support-sentinel)
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
@container (inline-size > 0) {
|
|
38
|
+
white-space: nowrap;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
|
|
3
|
+
import { FitText } from '../fit-text'
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof FitText> = {
|
|
6
|
+
args: { children: 'Fit Text', max: 'infinity * 1px', min: '1em' },
|
|
7
|
+
component: FitText,
|
|
8
|
+
title: 'Components/Effects/FitText'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default meta
|
|
12
|
+
|
|
13
|
+
type Story = StoryObj<typeof FitText>
|
|
14
|
+
|
|
15
|
+
export const Playground: Story = {}
|
|
16
|
+
|
|
17
|
+
export const Fills: Story = {
|
|
18
|
+
render: () => (
|
|
19
|
+
<div className="w-full">
|
|
20
|
+
<FitText className="font-sans font-bold">Design System</FitText>
|
|
21
|
+
</div>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const CappedMax: Story = {
|
|
26
|
+
render: () => (
|
|
27
|
+
<div className="w-full">
|
|
28
|
+
<FitText className="font-mondwest" max="4rem">
|
|
29
|
+
Capped max at 4rem
|
|
30
|
+
</FitText>
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { createElement } from 'react'
|
|
4
|
+
|
|
5
|
+
import { cn, type PolyProps, polyRef } from '../../../utils'
|
|
6
|
+
|
|
7
|
+
export const FitText = polyRef<'span', OwnProps>(
|
|
8
|
+
(
|
|
9
|
+
{ as, children, className, max, min = '1em', style: baseStyle, ...rest },
|
|
10
|
+
ref
|
|
11
|
+
) => {
|
|
12
|
+
if (typeof children !== 'string') {
|
|
13
|
+
return null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const style = {
|
|
17
|
+
'--fit-max': max ?? 'infinity * 1px',
|
|
18
|
+
'--fit-min': min,
|
|
19
|
+
...baseStyle
|
|
20
|
+
} as React.CSSProperties
|
|
21
|
+
|
|
22
|
+
return createElement(
|
|
23
|
+
(as ?? 'span') as React.ElementType,
|
|
24
|
+
{ ...rest, className: cn('fit-text', className), ref, style },
|
|
25
|
+
<>
|
|
26
|
+
<span>
|
|
27
|
+
<span>{children}</span>
|
|
28
|
+
</span>
|
|
29
|
+
|
|
30
|
+
<span aria-hidden="true">{children}</span>
|
|
31
|
+
</>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
interface OwnProps {
|
|
37
|
+
children: string
|
|
38
|
+
max?: string
|
|
39
|
+
min?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type FitTextProps<T extends React.ElementType = 'span'> = PolyProps<
|
|
43
|
+
T,
|
|
44
|
+
OwnProps
|
|
45
|
+
>
|