@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,113 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import { Checkbox } from './checkbox'
|
|
5
|
+
import { Small } from './typography/small'
|
|
6
|
+
|
|
7
|
+
function Demo({ disabled }: { disabled?: boolean }) {
|
|
8
|
+
const [checked, setChecked] = useState(false)
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div className="flex items-center gap-2.5">
|
|
12
|
+
<Checkbox
|
|
13
|
+
checked={checked}
|
|
14
|
+
disabled={disabled}
|
|
15
|
+
id="checkbox-demo"
|
|
16
|
+
onCheckedChange={setChecked}
|
|
17
|
+
/>
|
|
18
|
+
|
|
19
|
+
<label className="cursor-pointer text-sm" htmlFor="checkbox-demo">
|
|
20
|
+
Accept terms
|
|
21
|
+
</label>
|
|
22
|
+
</div>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const meta: Meta<typeof Checkbox> = {
|
|
27
|
+
component: Checkbox,
|
|
28
|
+
title: 'Components/Forms/Checkbox'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default meta
|
|
32
|
+
|
|
33
|
+
type Story = StoryObj<typeof Checkbox>
|
|
34
|
+
|
|
35
|
+
export const Playground: Story = { render: () => <Demo /> }
|
|
36
|
+
|
|
37
|
+
export const Disabled: Story = { render: () => <Demo disabled /> }
|
|
38
|
+
|
|
39
|
+
export const Checked: Story = {
|
|
40
|
+
render: () => (
|
|
41
|
+
<div className="flex items-center gap-2.5">
|
|
42
|
+
<Checkbox defaultChecked id="checkbox-checked" />
|
|
43
|
+
|
|
44
|
+
<label className="cursor-pointer text-sm" htmlFor="checkbox-checked">
|
|
45
|
+
Clone from default profile
|
|
46
|
+
</label>
|
|
47
|
+
</div>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const Stack: Story = {
|
|
52
|
+
render: () => {
|
|
53
|
+
function StackDemo() {
|
|
54
|
+
const [a, setA] = useState(true)
|
|
55
|
+
const [b, setB] = useState(false)
|
|
56
|
+
const [c, setC] = useState(true)
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div className="grid w-72 gap-3">
|
|
60
|
+
<div className="flex items-center gap-2.5">
|
|
61
|
+
<Checkbox checked={a} id="opt-a" onCheckedChange={setA} />
|
|
62
|
+
|
|
63
|
+
<label className="cursor-pointer text-sm" htmlFor="opt-a">
|
|
64
|
+
Logging
|
|
65
|
+
</label>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div className="flex items-center gap-2.5">
|
|
69
|
+
<Checkbox checked={b} id="opt-b" onCheckedChange={setB} />
|
|
70
|
+
|
|
71
|
+
<label className="cursor-pointer text-sm" htmlFor="opt-b">
|
|
72
|
+
Telemetry
|
|
73
|
+
</label>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<div className="flex items-center gap-2.5">
|
|
77
|
+
<Checkbox checked={c} id="opt-c" onCheckedChange={setC} />
|
|
78
|
+
|
|
79
|
+
<label className="cursor-pointer text-sm" htmlFor="opt-c">
|
|
80
|
+
Auto-update
|
|
81
|
+
</label>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return <StackDemo />
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const NextToLabel: Story = {
|
|
92
|
+
render: () => {
|
|
93
|
+
function LabeledDemo() {
|
|
94
|
+
const [checked, setChecked] = useState(true)
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div className="flex items-center gap-3">
|
|
98
|
+
<Small className="opacity-60 uppercase tracking-wider">
|
|
99
|
+
Persist globally
|
|
100
|
+
</Small>
|
|
101
|
+
|
|
102
|
+
<Checkbox
|
|
103
|
+
checked={checked}
|
|
104
|
+
id="persist-global"
|
|
105
|
+
onCheckedChange={setChecked}
|
|
106
|
+
/>
|
|
107
|
+
</div>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return <LabeledDemo />
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { forwardRef, type ComponentPropsWithoutRef, type ElementRef } from 'react'
|
|
4
|
+
import { Checkbox as CheckboxPrimitive } from 'radix-ui'
|
|
5
|
+
|
|
6
|
+
import { cn } from '../../utils'
|
|
7
|
+
|
|
8
|
+
import { CheckIcon } from './icons/check'
|
|
9
|
+
|
|
10
|
+
export const Checkbox = forwardRef<
|
|
11
|
+
ElementRef<typeof CheckboxPrimitive.Root>,
|
|
12
|
+
CheckboxProps
|
|
13
|
+
>(function Checkbox({ className, ...props }, ref) {
|
|
14
|
+
return (
|
|
15
|
+
<CheckboxPrimitive.Root
|
|
16
|
+
className={cn(
|
|
17
|
+
'peer flex h-4 w-4 shrink-0 cursor-pointer items-center justify-center border transition-colors outline-none',
|
|
18
|
+
'focus-visible:ring-1 focus-visible:ring-midground/30',
|
|
19
|
+
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
20
|
+
'data-[state=unchecked]:border-midground/20 data-[state=unchecked]:bg-background',
|
|
21
|
+
'data-[state=unchecked]:hover:border-midground/30',
|
|
22
|
+
'data-[state=checked]:border-midground/30 data-[state=checked]:bg-midground/15',
|
|
23
|
+
'data-[state=indeterminate]:border-midground/30 data-[state=indeterminate]:bg-midground/15',
|
|
24
|
+
className
|
|
25
|
+
)}
|
|
26
|
+
ref={ref}
|
|
27
|
+
{...props}
|
|
28
|
+
>
|
|
29
|
+
<CheckboxPrimitive.Indicator className="flex items-center justify-center text-current">
|
|
30
|
+
<CheckIcon className="h-3 w-3 text-midground" />
|
|
31
|
+
</CheckboxPrimitive.Indicator>
|
|
32
|
+
</CheckboxPrimitive.Root>
|
|
33
|
+
)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
type CheckboxProps = ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
CommandBlock,
|
|
5
|
+
CopyButton
|
|
6
|
+
} from './command-block'
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof CommandBlock> = {
|
|
9
|
+
args: {
|
|
10
|
+
code: 'curl -fsSL https://hermes.nousresearch.com/install.sh | bash',
|
|
11
|
+
label: '1. Install'
|
|
12
|
+
},
|
|
13
|
+
component: CommandBlock,
|
|
14
|
+
title: 'Components/Data Display/CommandBlock'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default meta
|
|
18
|
+
|
|
19
|
+
type Story = StoryObj<typeof CommandBlock>
|
|
20
|
+
|
|
21
|
+
export const Default: Story = {
|
|
22
|
+
render: args => (
|
|
23
|
+
<div className="w-[520px]">
|
|
24
|
+
<CommandBlock {...args} />
|
|
25
|
+
</div>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const TwoStep: Story = {
|
|
30
|
+
render: () => (
|
|
31
|
+
<div className="flex w-[520px] flex-col gap-3">
|
|
32
|
+
<CommandBlock
|
|
33
|
+
code="curl -fsSL https://hermes.nousresearch.com/install.sh | bash"
|
|
34
|
+
label="1. Install"
|
|
35
|
+
/>
|
|
36
|
+
|
|
37
|
+
<CommandBlock code="hermes setup" label="2. Configure" />
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const StandaloneButton: Story = {
|
|
43
|
+
render: () => (
|
|
44
|
+
<div className="flex items-center gap-3">
|
|
45
|
+
<span className="font-courier text-xs opacity-60">
|
|
46
|
+
echo "hello world"
|
|
47
|
+
</span>
|
|
48
|
+
|
|
49
|
+
<CopyButton text="echo 'hello world'" />
|
|
50
|
+
</div>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
import { cn } from '../../utils'
|
|
6
|
+
|
|
7
|
+
import { Small } from './typography/small'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A "copy to clipboard" button that briefly shows a "Copied!" confirmation.
|
|
11
|
+
* Designed to sit alongside a short command string, not as a general button.
|
|
12
|
+
*/
|
|
13
|
+
export function CopyButton({
|
|
14
|
+
children,
|
|
15
|
+
className,
|
|
16
|
+
copiedLabel = 'Copied!',
|
|
17
|
+
label = 'Copy',
|
|
18
|
+
resetDelayMs = 2000,
|
|
19
|
+
text
|
|
20
|
+
}: CopyButtonProps) {
|
|
21
|
+
const [copied, setCopied] = useState(false)
|
|
22
|
+
|
|
23
|
+
const handleCopy = useCallback(() => {
|
|
24
|
+
void navigator.clipboard.writeText(text).then(() => {
|
|
25
|
+
setCopied(true)
|
|
26
|
+
setTimeout(() => setCopied(false), resetDelayMs)
|
|
27
|
+
})
|
|
28
|
+
}, [resetDelayMs, text])
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<button
|
|
32
|
+
className={cn(
|
|
33
|
+
'font-courier text-display cursor-pointer border-none bg-transparent text-xs',
|
|
34
|
+
'tracking-widest',
|
|
35
|
+
'hover:text-midground tap-highlight-transparent transition-colors',
|
|
36
|
+
'flex items-center justify-center',
|
|
37
|
+
copied ? 'text-midground' : 'text-text-secondary',
|
|
38
|
+
className
|
|
39
|
+
)}
|
|
40
|
+
onClick={handleCopy}
|
|
41
|
+
type="button"
|
|
42
|
+
>
|
|
43
|
+
{children ?? (copied ? copiedLabel : label)}
|
|
44
|
+
</button>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A labeled, copy-able command (or code) display. Pairs `<CopyButton>` with
|
|
50
|
+
* a monospace code block. Used for install/setup instructions.
|
|
51
|
+
*/
|
|
52
|
+
export function CommandBlock({ className, code, label }: CommandBlockProps) {
|
|
53
|
+
return (
|
|
54
|
+
<div className={cn('flex flex-col gap-1', className)}>
|
|
55
|
+
<div className="flex items-center justify-between">
|
|
56
|
+
<Small className="opacity-50">{label}</Small>
|
|
57
|
+
|
|
58
|
+
<CopyButton text={code} />
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div
|
|
62
|
+
className={cn(
|
|
63
|
+
'bg-background/40 font-courier border border-current/20',
|
|
64
|
+
'px-3 py-2 text-[0.6875rem] leading-relaxed lowercase'
|
|
65
|
+
)}
|
|
66
|
+
>
|
|
67
|
+
<code className="break-all">{code}</code>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface CommandBlockProps {
|
|
74
|
+
className?: string
|
|
75
|
+
code: string
|
|
76
|
+
label: string
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface CopyButtonProps {
|
|
80
|
+
children?: React.ReactNode
|
|
81
|
+
className?: string
|
|
82
|
+
copiedLabel?: string
|
|
83
|
+
label?: string
|
|
84
|
+
resetDelayMs?: number
|
|
85
|
+
text: string
|
|
86
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import { Button } from './button'
|
|
5
|
+
import { ConfirmDialog } from './confirm-dialog'
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof ConfirmDialog> = {
|
|
8
|
+
component: ConfirmDialog,
|
|
9
|
+
title: 'Components/Overlays/ConfirmDialog'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default meta
|
|
13
|
+
|
|
14
|
+
type Story = StoryObj<typeof ConfirmDialog>
|
|
15
|
+
|
|
16
|
+
export const Default: Story = {
|
|
17
|
+
render: () => {
|
|
18
|
+
function Demo() {
|
|
19
|
+
const [open, setOpen] = useState(false)
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<Button onClick={() => setOpen(true)}>Open dialog</Button>
|
|
24
|
+
|
|
25
|
+
<ConfirmDialog
|
|
26
|
+
description="This action cannot be undone."
|
|
27
|
+
onCancel={() => setOpen(false)}
|
|
28
|
+
onConfirm={() => setOpen(false)}
|
|
29
|
+
open={open}
|
|
30
|
+
title="Are you sure?"
|
|
31
|
+
/>
|
|
32
|
+
</>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return <Demo />
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const Destructive: Story = {
|
|
41
|
+
render: () => {
|
|
42
|
+
function Demo() {
|
|
43
|
+
const [open, setOpen] = useState(false)
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<>
|
|
47
|
+
<Button destructive onClick={() => setOpen(true)}>
|
|
48
|
+
Delete item
|
|
49
|
+
</Button>
|
|
50
|
+
|
|
51
|
+
<ConfirmDialog
|
|
52
|
+
confirmLabel="Delete"
|
|
53
|
+
description="This will permanently delete the item. This action cannot be undone."
|
|
54
|
+
destructive
|
|
55
|
+
onCancel={() => setOpen(false)}
|
|
56
|
+
onConfirm={() => setOpen(false)}
|
|
57
|
+
open={open}
|
|
58
|
+
title="Delete item?"
|
|
59
|
+
/>
|
|
60
|
+
</>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return <Demo />
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const Loading: Story = {
|
|
69
|
+
render: () => {
|
|
70
|
+
function Demo() {
|
|
71
|
+
const [open, setOpen] = useState(false)
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<>
|
|
75
|
+
<Button onClick={() => setOpen(true)}>With loading state</Button>
|
|
76
|
+
|
|
77
|
+
<ConfirmDialog
|
|
78
|
+
description="Simulating a loading state."
|
|
79
|
+
loading
|
|
80
|
+
onCancel={() => setOpen(false)}
|
|
81
|
+
onConfirm={() => {}}
|
|
82
|
+
open={open}
|
|
83
|
+
title="Processing…"
|
|
84
|
+
/>
|
|
85
|
+
</>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return <Demo />
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useRef } from 'react'
|
|
4
|
+
import { AlertDialog as AlertDialogPrimitive } from 'radix-ui'
|
|
5
|
+
|
|
6
|
+
import { cn } from '../../utils'
|
|
7
|
+
import { Button } from './button'
|
|
8
|
+
|
|
9
|
+
function WarningTriangle({ className }: { className?: string }) {
|
|
10
|
+
return (
|
|
11
|
+
<svg
|
|
12
|
+
aria-hidden
|
|
13
|
+
className={className}
|
|
14
|
+
fill="none"
|
|
15
|
+
stroke="currentColor"
|
|
16
|
+
strokeLinecap="round"
|
|
17
|
+
strokeLinejoin="round"
|
|
18
|
+
strokeWidth={2}
|
|
19
|
+
viewBox="0 0 24 24"
|
|
20
|
+
>
|
|
21
|
+
<path d="m10.29 3.86-8.16 14a2 2 0 0 0 1.73 3h16.28a2 2 0 0 0 1.73-3l-8.16-14a2 2 0 0 0-3.46 0z" />
|
|
22
|
+
<line x1="12" x2="12" y1="9" y2="13" />
|
|
23
|
+
<line x1="12" x2="12.01" y1="17" y2="17" />
|
|
24
|
+
</svg>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function ConfirmDialog({
|
|
29
|
+
cancelLabel = 'Cancel',
|
|
30
|
+
confirmLabel = 'Confirm',
|
|
31
|
+
description,
|
|
32
|
+
destructive = false,
|
|
33
|
+
loading = false,
|
|
34
|
+
onCancel,
|
|
35
|
+
onConfirm,
|
|
36
|
+
open,
|
|
37
|
+
title
|
|
38
|
+
}: ConfirmDialogProps) {
|
|
39
|
+
const confirmedRef = useRef(false)
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<AlertDialogPrimitive.Root
|
|
43
|
+
onOpenChange={v => {
|
|
44
|
+
if (!v && !confirmedRef.current) onCancel()
|
|
45
|
+
confirmedRef.current = false
|
|
46
|
+
}}
|
|
47
|
+
open={open}
|
|
48
|
+
>
|
|
49
|
+
<AlertDialogPrimitive.Portal>
|
|
50
|
+
<AlertDialogPrimitive.Overlay
|
|
51
|
+
className={cn(
|
|
52
|
+
'fixed inset-0 z-50',
|
|
53
|
+
'bg-black/60 backdrop-blur-sm',
|
|
54
|
+
'data-[state=open]:animate-in data-[state=open]:fade-in-0',
|
|
55
|
+
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0'
|
|
56
|
+
)}
|
|
57
|
+
/>
|
|
58
|
+
|
|
59
|
+
<AlertDialogPrimitive.Content
|
|
60
|
+
className={cn(
|
|
61
|
+
'fixed top-1/2 left-1/2 z-50 -translate-x-1/2 -translate-y-1/2',
|
|
62
|
+
'w-[calc(100%-2rem)] max-w-md',
|
|
63
|
+
'border border-midground/15 bg-background-base text-foreground-base shadow-lg outline-none',
|
|
64
|
+
'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
|
65
|
+
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
|
|
66
|
+
'duration-150'
|
|
67
|
+
)}
|
|
68
|
+
>
|
|
69
|
+
<div className="flex items-start gap-3 p-4 border-b border-midground/15">
|
|
70
|
+
{destructive && (
|
|
71
|
+
<div aria-hidden className="mt-0.5 shrink-0 text-destructive">
|
|
72
|
+
<WarningTriangle className="h-4 w-4" />
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
|
|
76
|
+
<div className="flex-1 min-w-0 flex flex-col gap-1">
|
|
77
|
+
<AlertDialogPrimitive.Title
|
|
78
|
+
className="font-expanded text-sm font-bold tracking-[0.08em] uppercase"
|
|
79
|
+
>
|
|
80
|
+
{title}
|
|
81
|
+
</AlertDialogPrimitive.Title>
|
|
82
|
+
|
|
83
|
+
{description && (
|
|
84
|
+
<AlertDialogPrimitive.Description
|
|
85
|
+
className="font-mondwest text-xs text-midground/60 leading-relaxed"
|
|
86
|
+
>
|
|
87
|
+
{description}
|
|
88
|
+
</AlertDialogPrimitive.Description>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div className="flex items-center justify-end gap-2 p-3">
|
|
94
|
+
<AlertDialogPrimitive.Cancel asChild>
|
|
95
|
+
<Button disabled={loading} outlined type="button">
|
|
96
|
+
{cancelLabel}
|
|
97
|
+
</Button>
|
|
98
|
+
</AlertDialogPrimitive.Cancel>
|
|
99
|
+
|
|
100
|
+
<AlertDialogPrimitive.Action asChild>
|
|
101
|
+
<Button
|
|
102
|
+
destructive={destructive}
|
|
103
|
+
disabled={loading}
|
|
104
|
+
onClick={() => {
|
|
105
|
+
confirmedRef.current = true
|
|
106
|
+
onConfirm()
|
|
107
|
+
}}
|
|
108
|
+
type="button"
|
|
109
|
+
>
|
|
110
|
+
{loading ? '…' : confirmLabel}
|
|
111
|
+
</Button>
|
|
112
|
+
</AlertDialogPrimitive.Action>
|
|
113
|
+
</div>
|
|
114
|
+
</AlertDialogPrimitive.Content>
|
|
115
|
+
</AlertDialogPrimitive.Portal>
|
|
116
|
+
</AlertDialogPrimitive.Root>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface ConfirmDialogProps {
|
|
121
|
+
cancelLabel?: string
|
|
122
|
+
confirmLabel?: string
|
|
123
|
+
description?: string
|
|
124
|
+
destructive?: boolean
|
|
125
|
+
loading?: boolean
|
|
126
|
+
onCancel: () => void
|
|
127
|
+
onConfirm: () => void
|
|
128
|
+
open: boolean
|
|
129
|
+
title: string
|
|
130
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from 'react'
|
|
4
|
+
|
|
5
|
+
const INTERACTIVE =
|
|
6
|
+
'a, button, [role="button"], input, textarea, select, [data-cursor]'
|
|
7
|
+
|
|
8
|
+
const HAND =
|
|
9
|
+
'M6.84 21.83c-.47-.6-1.05-1.82-2.07-3.34-.58-.83-2.01-2.41-2.45-3.23a2.1 2.1 0 0 1-.25-1.67 2.2 2.2 0 0 1 2.39-1.67c.85.18 1.63.6 2.25 1.2.43.41.82.85 1.18 1.32.27.34.33.47.63.85.3.39.5.77.35.2-.11-.83-.31-2.23-.6-3.48-.21-.95-.26-1.1-.46-1.82s-.32-1.32-.54-2.13c-.2-.8-.35-1.62-.46-2.44a4.7 4.7 0 0 1 .43-3.08c.58-.55 1.44-.7 2.17-.37a4.4 4.4 0 0 1 1.57 2.17c.43 1.07.72 2.19.86 3.33.27 1.67.79 4.1.8 4.6 0-.61-.11-1.91 0-2.5.12-.6.54-1.1 1.12-1.33.5-.15 1.02-.19 1.53-.1.52.1.98.4 1.29.83.38.98.6 2 .63 3.05.04-.91.2-1.82.47-2.7.28-.39.68-.67 1.15-.8.55-.1 1.11-.1 1.66 0 .46.15.85.44 1.14.82.35.88.56 1.82.63 2.77 0 .23.12-.65.48-1.24a1.67 1.67 0 1 1 3.17 1.07v3.77c-.06.97-.2 1.94-.4 2.9-.29.85-.7 1.65-1.2 2.38-.8.9-1.48 1.92-1.98 3.02a6.67 6.67 0 0 0 .03 3.2c-.68.07-1.37.07-2.05 0-.65-.1-1.45-1.4-1.67-1.8a.63.63 0 0 0-1.13 0c-.37.64-1.18 1.79-1.75 1.85-1.12.14-3.42 0-5.23 0 0 0 .3-1.66-.39-2.27-.68-.6-1.38-1.3-1.9-1.76l-1.4-1.6Z'
|
|
10
|
+
|
|
11
|
+
export function Cursor({ scale = 0.8 }: { scale?: number }) {
|
|
12
|
+
const $root = useRef<HTMLDivElement>(null)
|
|
13
|
+
const $arrow = useRef<HTMLDivElement>(null)
|
|
14
|
+
const $ptr = useRef<HTMLDivElement>(null)
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const [root, arrow, ptr] = [$root.current, $arrow.current, $ptr.current]
|
|
18
|
+
|
|
19
|
+
if (!root || !arrow || !ptr) {
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const on = (
|
|
24
|
+
el: EventTarget,
|
|
25
|
+
ev: string,
|
|
26
|
+
fn: EventListener,
|
|
27
|
+
opts?: AddEventListenerOptions
|
|
28
|
+
) => {
|
|
29
|
+
el.addEventListener(ev, fn, opts)
|
|
30
|
+
|
|
31
|
+
return () => el.removeEventListener(ev, fn)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return [
|
|
35
|
+
on(
|
|
36
|
+
document,
|
|
37
|
+
'mousemove',
|
|
38
|
+
(e: Event) => {
|
|
39
|
+
const { clientX: x, clientY: y } = e as MouseEvent
|
|
40
|
+
root.style.translate = `${x}px ${y}px`
|
|
41
|
+
root.style.opacity = '1'
|
|
42
|
+
},
|
|
43
|
+
{ passive: true }
|
|
44
|
+
),
|
|
45
|
+
|
|
46
|
+
on(
|
|
47
|
+
document,
|
|
48
|
+
'mouseover',
|
|
49
|
+
(e: Event) => {
|
|
50
|
+
const isPtr = !!(e.target as HTMLElement).closest?.(INTERACTIVE)
|
|
51
|
+
arrow.style.opacity = isPtr ? '0' : '1'
|
|
52
|
+
ptr.style.opacity = isPtr ? '1' : '0'
|
|
53
|
+
},
|
|
54
|
+
{ passive: true }
|
|
55
|
+
),
|
|
56
|
+
|
|
57
|
+
on(document, 'mousedown', () => {
|
|
58
|
+
root.style.transform = 'translate(1px, 1px)'
|
|
59
|
+
}),
|
|
60
|
+
on(document, 'mouseup', () => {
|
|
61
|
+
root.style.transform = ''
|
|
62
|
+
}),
|
|
63
|
+
on(document.documentElement, 'mouseleave', () => {
|
|
64
|
+
root.style.opacity = '0'
|
|
65
|
+
}),
|
|
66
|
+
on(document.documentElement, 'mouseenter', () => {
|
|
67
|
+
root.style.opacity = '1'
|
|
68
|
+
})
|
|
69
|
+
].reduce((_, fn) => fn, undefined as unknown as void)
|
|
70
|
+
}, [])
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div
|
|
74
|
+
aria-hidden
|
|
75
|
+
ref={$root}
|
|
76
|
+
style={{
|
|
77
|
+
filter: 'drop-shadow(1px 2px 0 #000)',
|
|
78
|
+
height: 32 * scale,
|
|
79
|
+
left: 0,
|
|
80
|
+
opacity: 0,
|
|
81
|
+
pointerEvents: 'none',
|
|
82
|
+
position: 'fixed',
|
|
83
|
+
top: 0,
|
|
84
|
+
width: 32 * scale,
|
|
85
|
+
willChange: 'translate',
|
|
86
|
+
zIndex: 9999
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
<div ref={$arrow} style={{ inset: 0, position: 'absolute' }}>
|
|
90
|
+
<svg viewBox="0 0 16 16">
|
|
91
|
+
<path
|
|
92
|
+
d="M1 1L1 14L5 10L8 15L10 14L7 9L12 9L1 1Z"
|
|
93
|
+
fill="#fff"
|
|
94
|
+
stroke="#000"
|
|
95
|
+
strokeLinejoin="round"
|
|
96
|
+
strokeWidth={1}
|
|
97
|
+
/>
|
|
98
|
+
</svg>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div ref={$ptr} style={{ inset: 0, opacity: 0, position: 'absolute' }}>
|
|
102
|
+
<svg viewBox="0 0 28 29">
|
|
103
|
+
<path
|
|
104
|
+
d={HAND}
|
|
105
|
+
fill="#fff"
|
|
106
|
+
stroke="#000"
|
|
107
|
+
strokeLinejoin="round"
|
|
108
|
+
strokeWidth={2}
|
|
109
|
+
style={{ paintOrder: 'stroke fill' }}
|
|
110
|
+
/>
|
|
111
|
+
</svg>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
)
|
|
115
|
+
}
|