@nous-research/ui 0.14.2 → 0.16.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 +227 -0
- package/README.md +24 -4
- package/dist/fonts.js +1 -0
- package/dist/hooks/use-capped-frame.js +1 -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/index.js +1 -0
- 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/button.js +2 -1
- package/dist/ui/components/checkbox.js +1 -0
- package/dist/ui/components/command-block.js +4 -3
- package/dist/ui/components/cursor.js +1 -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/leva-client.js +1 -0
- package/dist/ui/components/list-item.js +3 -2
- package/dist/ui/components/modal/index.js +1 -0
- package/dist/ui/components/modal/modal.css +1 -1
- 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/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/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 +33 -1
- 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 +4 -2
- 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-capped-frame.ts +18 -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/index.ts +109 -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/button.stories.tsx +68 -0
- package/src/ui/components/button.tsx +170 -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/cursor.tsx +115 -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/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/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/modal/index.stories.tsx +46 -0
- package/src/ui/components/modal/index.tsx +48 -0
- package/src/ui/components/modal/modal.css +36 -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/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/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 +383 -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
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import { Switch } from './switch'
|
|
5
|
+
import { Small } from './typography/small'
|
|
6
|
+
|
|
7
|
+
function Demo({ disabled }: { disabled?: boolean }) {
|
|
8
|
+
const [checked, setChecked] = useState(false)
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<Switch checked={checked} disabled={disabled} onCheckedChange={setChecked} />
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const meta: Meta<typeof Switch> = {
|
|
16
|
+
component: Switch,
|
|
17
|
+
title: 'Components/Switch'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default meta
|
|
21
|
+
|
|
22
|
+
type Story = StoryObj<typeof Switch>
|
|
23
|
+
|
|
24
|
+
export const Playground: Story = { render: () => <Demo /> }
|
|
25
|
+
|
|
26
|
+
export const Disabled: Story = { render: () => <Demo disabled /> }
|
|
27
|
+
|
|
28
|
+
export const NextToLabel: Story = {
|
|
29
|
+
render: () => {
|
|
30
|
+
function LabeledDemo() {
|
|
31
|
+
const [checked, setChecked] = useState(true)
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<label className="flex items-center gap-3">
|
|
35
|
+
<Small className="opacity-60 uppercase tracking-wider">
|
|
36
|
+
Enable feature
|
|
37
|
+
</Small>
|
|
38
|
+
|
|
39
|
+
<Switch checked={checked} onCheckedChange={setChecked} />
|
|
40
|
+
</label>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return <LabeledDemo />
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const Stack: Story = {
|
|
49
|
+
render: () => {
|
|
50
|
+
function StackDemo() {
|
|
51
|
+
const [a, setA] = useState(true)
|
|
52
|
+
const [b, setB] = useState(false)
|
|
53
|
+
const [c, setC] = useState(true)
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className="grid w-64 gap-3">
|
|
57
|
+
<label className="flex items-center justify-between">
|
|
58
|
+
<Small className="opacity-60 uppercase tracking-wider">Logging</Small>
|
|
59
|
+
<Switch checked={a} onCheckedChange={setA} />
|
|
60
|
+
</label>
|
|
61
|
+
|
|
62
|
+
<label className="flex items-center justify-between">
|
|
63
|
+
<Small className="opacity-60 uppercase tracking-wider">Telemetry</Small>
|
|
64
|
+
<Switch checked={b} onCheckedChange={setB} />
|
|
65
|
+
</label>
|
|
66
|
+
|
|
67
|
+
<label className="flex items-center justify-between">
|
|
68
|
+
<Small className="opacity-60 uppercase tracking-wider">Auto-update</Small>
|
|
69
|
+
<Switch checked={c} onCheckedChange={setC} />
|
|
70
|
+
</label>
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return <StackDemo />
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { type ButtonHTMLAttributes, forwardRef } from 'react'
|
|
4
|
+
|
|
5
|
+
import { cn } from '../../utils'
|
|
6
|
+
|
|
7
|
+
export const Switch = forwardRef<HTMLButtonElement, SwitchProps>(function Switch(
|
|
8
|
+
{ checked, className, disabled, id, onCheckedChange, ...props },
|
|
9
|
+
ref
|
|
10
|
+
) {
|
|
11
|
+
return (
|
|
12
|
+
<button
|
|
13
|
+
aria-checked={checked}
|
|
14
|
+
className={cn(
|
|
15
|
+
'peer inline-flex h-5 w-9 shrink-0 items-center border transition-colors cursor-pointer',
|
|
16
|
+
'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-midground/30',
|
|
17
|
+
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
18
|
+
checked
|
|
19
|
+
? 'bg-midground/15 border-midground/30'
|
|
20
|
+
: 'bg-background border-midground/20',
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
disabled={disabled}
|
|
24
|
+
id={id}
|
|
25
|
+
onClick={() => onCheckedChange(!checked)}
|
|
26
|
+
ref={ref}
|
|
27
|
+
role="switch"
|
|
28
|
+
type="button"
|
|
29
|
+
{...props}
|
|
30
|
+
>
|
|
31
|
+
<span
|
|
32
|
+
aria-hidden
|
|
33
|
+
className={cn(
|
|
34
|
+
'pointer-events-none block h-3.5 w-3.5 transition-transform',
|
|
35
|
+
checked
|
|
36
|
+
? 'translate-x-4 bg-midground'
|
|
37
|
+
: 'translate-x-0.5 bg-midground/40'
|
|
38
|
+
)}
|
|
39
|
+
/>
|
|
40
|
+
</button>
|
|
41
|
+
)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
interface SwitchProps
|
|
45
|
+
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onChange'> {
|
|
46
|
+
checked: boolean
|
|
47
|
+
onCheckedChange: (checked: boolean) => void
|
|
48
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
|
|
3
|
+
import { Tabs, TabsList, TabsTrigger } from './tabs'
|
|
4
|
+
import { Small } from './typography/small'
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Tabs> = {
|
|
7
|
+
component: Tabs,
|
|
8
|
+
title: 'Components/Tabs'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default meta
|
|
12
|
+
|
|
13
|
+
type Story = StoryObj<typeof Tabs>
|
|
14
|
+
|
|
15
|
+
export const Playground: Story = {
|
|
16
|
+
render: () => (
|
|
17
|
+
<div className="w-[28rem]">
|
|
18
|
+
<Tabs defaultValue="overview">
|
|
19
|
+
{(active, setActive) => (
|
|
20
|
+
<>
|
|
21
|
+
<TabsList>
|
|
22
|
+
<TabsTrigger
|
|
23
|
+
active={active === 'overview'}
|
|
24
|
+
onClick={() => setActive('overview')}
|
|
25
|
+
value="overview"
|
|
26
|
+
>
|
|
27
|
+
Overview
|
|
28
|
+
</TabsTrigger>
|
|
29
|
+
|
|
30
|
+
<TabsTrigger
|
|
31
|
+
active={active === 'metrics'}
|
|
32
|
+
onClick={() => setActive('metrics')}
|
|
33
|
+
value="metrics"
|
|
34
|
+
>
|
|
35
|
+
Metrics
|
|
36
|
+
</TabsTrigger>
|
|
37
|
+
|
|
38
|
+
<TabsTrigger
|
|
39
|
+
active={active === 'logs'}
|
|
40
|
+
onClick={() => setActive('logs')}
|
|
41
|
+
value="logs"
|
|
42
|
+
>
|
|
43
|
+
Logs
|
|
44
|
+
</TabsTrigger>
|
|
45
|
+
|
|
46
|
+
<TabsTrigger
|
|
47
|
+
active={active === 'settings'}
|
|
48
|
+
onClick={() => setActive('settings')}
|
|
49
|
+
value="settings"
|
|
50
|
+
>
|
|
51
|
+
Settings
|
|
52
|
+
</TabsTrigger>
|
|
53
|
+
</TabsList>
|
|
54
|
+
|
|
55
|
+
<div className="p-4 border border-midground/10 bg-background-base">
|
|
56
|
+
<Small className="opacity-60">
|
|
57
|
+
Active panel: <span className="opacity-100">{active}</span>
|
|
58
|
+
</Small>
|
|
59
|
+
</div>
|
|
60
|
+
</>
|
|
61
|
+
)}
|
|
62
|
+
</Tabs>
|
|
63
|
+
</div>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const TwoTabs: Story = {
|
|
68
|
+
render: () => (
|
|
69
|
+
<div className="w-80">
|
|
70
|
+
<Tabs defaultValue="local">
|
|
71
|
+
{(active, setActive) => (
|
|
72
|
+
<>
|
|
73
|
+
<TabsList>
|
|
74
|
+
<TabsTrigger
|
|
75
|
+
active={active === 'local'}
|
|
76
|
+
onClick={() => setActive('local')}
|
|
77
|
+
value="local"
|
|
78
|
+
>
|
|
79
|
+
Local
|
|
80
|
+
</TabsTrigger>
|
|
81
|
+
|
|
82
|
+
<TabsTrigger
|
|
83
|
+
active={active === 'remote'}
|
|
84
|
+
onClick={() => setActive('remote')}
|
|
85
|
+
value="remote"
|
|
86
|
+
>
|
|
87
|
+
Remote
|
|
88
|
+
</TabsTrigger>
|
|
89
|
+
</TabsList>
|
|
90
|
+
|
|
91
|
+
<div className="p-4 border border-midground/10 bg-background-base">
|
|
92
|
+
<Small className="opacity-60">
|
|
93
|
+
Active panel: <span className="opacity-100">{active}</span>
|
|
94
|
+
</Small>
|
|
95
|
+
</div>
|
|
96
|
+
</>
|
|
97
|
+
)}
|
|
98
|
+
</Tabs>
|
|
99
|
+
</div>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type ButtonHTMLAttributes,
|
|
5
|
+
type HTMLAttributes,
|
|
6
|
+
type ReactNode,
|
|
7
|
+
useState
|
|
8
|
+
} from 'react'
|
|
9
|
+
|
|
10
|
+
import { cn } from '../../utils'
|
|
11
|
+
|
|
12
|
+
export function Tabs({ children, className, defaultValue }: TabsProps) {
|
|
13
|
+
const [active, setActive] = useState(defaultValue)
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className={cn('flex flex-col gap-4', className)}>
|
|
17
|
+
{children(active, setActive)}
|
|
18
|
+
</div>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function TabsList({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
className={cn(
|
|
26
|
+
'inline-flex h-9 items-center justify-start border-b border-midground/15 text-text-secondary',
|
|
27
|
+
className
|
|
28
|
+
)}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function TabsTrigger({
|
|
35
|
+
active,
|
|
36
|
+
className,
|
|
37
|
+
value: _value,
|
|
38
|
+
...props
|
|
39
|
+
}: TabsTriggerProps) {
|
|
40
|
+
return (
|
|
41
|
+
<button
|
|
42
|
+
className={cn(
|
|
43
|
+
'relative inline-flex items-center justify-center whitespace-nowrap px-3 py-1.5',
|
|
44
|
+
'font-mondwest text-display text-xs tracking-[0.1em] transition-all cursor-pointer',
|
|
45
|
+
'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-midground/30',
|
|
46
|
+
active
|
|
47
|
+
? 'text-midground after:absolute after:bottom-0 after:left-0 after:right-0 after:h-px after:bg-midground'
|
|
48
|
+
: 'text-text-secondary hover:text-midground',
|
|
49
|
+
className
|
|
50
|
+
)}
|
|
51
|
+
type="button"
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface TabsProps {
|
|
58
|
+
children: (active: string, setActive: (value: string) => void) => ReactNode
|
|
59
|
+
className?: string
|
|
60
|
+
defaultValue: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface TabsTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
64
|
+
active: boolean
|
|
65
|
+
value: string
|
|
66
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
TerminalDemo,
|
|
5
|
+
type TerminalDemoStep
|
|
6
|
+
} from './terminal-demo'
|
|
7
|
+
|
|
8
|
+
const SEQUENCE: TerminalDemoStep[] = [
|
|
9
|
+
{ text: '❯ ', type: 'prompt' },
|
|
10
|
+
{
|
|
11
|
+
delay: 30,
|
|
12
|
+
text: 'Research the latest approaches to GRPO training and write a summary',
|
|
13
|
+
type: 'type'
|
|
14
|
+
},
|
|
15
|
+
{ ms: 600, type: 'pause' },
|
|
16
|
+
{
|
|
17
|
+
lines: [
|
|
18
|
+
'',
|
|
19
|
+
'<span class="opacity-50"> web_search "GRPO reinforcement learning" 1.2s</span>',
|
|
20
|
+
'<span class="opacity-50"> web_extract arxiv.org/abs/2402.03300 3.1s</span>',
|
|
21
|
+
'<span class="opacity-50"> write_file ~/research/grpo-summary.md 0.1s</span>'
|
|
22
|
+
],
|
|
23
|
+
type: 'output'
|
|
24
|
+
},
|
|
25
|
+
{ ms: 500, type: 'pause' },
|
|
26
|
+
{
|
|
27
|
+
lines: [
|
|
28
|
+
'',
|
|
29
|
+
'<span class="opacity-70">Done! I\'ve written a summary covering:</span>',
|
|
30
|
+
'',
|
|
31
|
+
'<span class="opacity-70"> <span class="text-midground">✓</span> GRPO\'s group-relative advantage</span>',
|
|
32
|
+
'<span class="opacity-70"> <span class="text-midground">✓</span> Comparison with PPO/DPO</span>',
|
|
33
|
+
'',
|
|
34
|
+
'<span class="opacity-70">Saved to</span> <span class="text-midground">~/research/grpo-summary.md</span>'
|
|
35
|
+
],
|
|
36
|
+
type: 'output'
|
|
37
|
+
},
|
|
38
|
+
{ ms: 2500, type: 'pause' },
|
|
39
|
+
{ type: 'clear' }
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
const meta: Meta<typeof TerminalDemo> = {
|
|
43
|
+
args: { label: 'Hermes', sequence: SEQUENCE },
|
|
44
|
+
component: TerminalDemo,
|
|
45
|
+
title: 'Components/TerminalDemo'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default meta
|
|
49
|
+
|
|
50
|
+
type Story = StoryObj<typeof TerminalDemo>
|
|
51
|
+
|
|
52
|
+
export const Default: Story = {
|
|
53
|
+
render: args => (
|
|
54
|
+
<div className="w-[640px]">
|
|
55
|
+
<TerminalDemo {...args} />
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const TallerWindow: Story = {
|
|
61
|
+
args: { height: 480, label: 'shell' },
|
|
62
|
+
render: args => (
|
|
63
|
+
<div className="w-[640px]">
|
|
64
|
+
<TerminalDemo {...args} />
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
import { cn } from '../../utils'
|
|
6
|
+
|
|
7
|
+
function sleep(ms: number) {
|
|
8
|
+
return new Promise<void>(resolve => setTimeout(resolve, ms))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function TerminalDemo({
|
|
12
|
+
ariaLabel = 'Terminal Demo',
|
|
13
|
+
className,
|
|
14
|
+
height = 320,
|
|
15
|
+
label = 'Terminal',
|
|
16
|
+
loopDelayMs = 1000,
|
|
17
|
+
outputLineDelayMs = 50,
|
|
18
|
+
sequence
|
|
19
|
+
}: TerminalDemoProps) {
|
|
20
|
+
const bodyRef = useRef<HTMLDivElement>(null)
|
|
21
|
+
const startedRef = useRef(false)
|
|
22
|
+
const [html, setHtml] = useState('')
|
|
23
|
+
|
|
24
|
+
const runDemo = useCallback(async () => {
|
|
25
|
+
if (startedRef.current) {
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
startedRef.current = true
|
|
30
|
+
let content = ''
|
|
31
|
+
|
|
32
|
+
const render = (h: string) => {
|
|
33
|
+
content = h
|
|
34
|
+
setHtml(h)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (;;) {
|
|
38
|
+
for (const step of sequence) {
|
|
39
|
+
switch (step.type) {
|
|
40
|
+
case 'clear':
|
|
41
|
+
content = ''
|
|
42
|
+
render('')
|
|
43
|
+
|
|
44
|
+
break
|
|
45
|
+
|
|
46
|
+
case 'output':
|
|
47
|
+
for (const line of step.lines) {
|
|
48
|
+
render(content + '\n' + line)
|
|
49
|
+
await sleep(outputLineDelayMs)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
break
|
|
53
|
+
|
|
54
|
+
case 'pause':
|
|
55
|
+
await sleep(step.ms)
|
|
56
|
+
|
|
57
|
+
break
|
|
58
|
+
|
|
59
|
+
case 'prompt':
|
|
60
|
+
render(content + `<span class="text-midground">${step.text}</span>`)
|
|
61
|
+
|
|
62
|
+
break
|
|
63
|
+
|
|
64
|
+
case 'type':
|
|
65
|
+
for (const char of step.text) {
|
|
66
|
+
render(content + char)
|
|
67
|
+
await sleep(step.delay ?? 30)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
break
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
content = ''
|
|
75
|
+
render('')
|
|
76
|
+
await sleep(loopDelayMs)
|
|
77
|
+
}
|
|
78
|
+
}, [loopDelayMs, outputLineDelayMs, sequence])
|
|
79
|
+
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
const el = bodyRef.current?.closest('[data-demo-root]')
|
|
82
|
+
|
|
83
|
+
if (!el) {
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const observer = new IntersectionObserver(
|
|
88
|
+
entries => {
|
|
89
|
+
entries.forEach(entry => {
|
|
90
|
+
if (entry.isIntersecting) {
|
|
91
|
+
runDemo()
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
},
|
|
95
|
+
{ threshold: 0.3 }
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
observer.observe(el)
|
|
99
|
+
|
|
100
|
+
return () => observer.disconnect()
|
|
101
|
+
}, [runDemo])
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (bodyRef.current) {
|
|
105
|
+
bodyRef.current.scrollTop = bodyRef.current.scrollHeight
|
|
106
|
+
}
|
|
107
|
+
}, [html])
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<div
|
|
111
|
+
aria-label={ariaLabel}
|
|
112
|
+
className={cn('border-4 border-double border-inherit', className)}
|
|
113
|
+
data-demo-root
|
|
114
|
+
role="img"
|
|
115
|
+
>
|
|
116
|
+
<div className="flex items-center gap-3 border-b border-current/10 px-3 py-2">
|
|
117
|
+
<div className="flex gap-1.5">
|
|
118
|
+
<span
|
|
119
|
+
className="bg-midground size-2 rounded-full"
|
|
120
|
+
style={{ mixBlendMode: 'plus-lighter' }}
|
|
121
|
+
/>
|
|
122
|
+
|
|
123
|
+
<span className="bg-midground/60 size-2 rounded-full" />
|
|
124
|
+
<span className="bg-midground/30 size-2 rounded-full" />
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<span className="font-courier text-display text-xs tracking-widest text-text-tertiary">
|
|
128
|
+
{label}
|
|
129
|
+
</span>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<div
|
|
133
|
+
className={cn(
|
|
134
|
+
'overflow-x-hidden overflow-y-auto whitespace-pre-wrap',
|
|
135
|
+
'font-courier p-4 text-[0.75rem] leading-[1.7] normal-case'
|
|
136
|
+
)}
|
|
137
|
+
dangerouslySetInnerHTML={{
|
|
138
|
+
__html:
|
|
139
|
+
html +
|
|
140
|
+
'<span class="blink inline-block dither ml-0.5 h-[1em] w-[1ch]"></span>'
|
|
141
|
+
}}
|
|
142
|
+
ref={bodyRef}
|
|
143
|
+
style={{ height }}
|
|
144
|
+
/>
|
|
145
|
+
</div>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
interface ClearStep {
|
|
150
|
+
type: 'clear'
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
interface OutputStep {
|
|
154
|
+
lines: string[]
|
|
155
|
+
type: 'output'
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
interface PauseStep {
|
|
159
|
+
ms: number
|
|
160
|
+
type: 'pause'
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
interface PromptStep {
|
|
164
|
+
text: string
|
|
165
|
+
type: 'prompt'
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
interface TerminalDemoProps {
|
|
169
|
+
ariaLabel?: string
|
|
170
|
+
className?: string
|
|
171
|
+
height?: number | string
|
|
172
|
+
label?: string
|
|
173
|
+
loopDelayMs?: number
|
|
174
|
+
outputLineDelayMs?: number
|
|
175
|
+
sequence: TerminalDemoStep[]
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export type TerminalDemoStep =
|
|
179
|
+
| ClearStep
|
|
180
|
+
| OutputStep
|
|
181
|
+
| PauseStep
|
|
182
|
+
| PromptStep
|
|
183
|
+
| TypeStep
|
|
184
|
+
|
|
185
|
+
interface TypeStep {
|
|
186
|
+
delay?: number
|
|
187
|
+
text: string
|
|
188
|
+
type: 'type'
|
|
189
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import { HamburgerIcon } from './icons'
|
|
5
|
+
import { ThemeToggle } from './theme-toggle'
|
|
6
|
+
import { Small } from './typography/small'
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof ThemeToggle> = {
|
|
9
|
+
component: ThemeToggle,
|
|
10
|
+
title: 'Components/ThemeToggle'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default meta
|
|
14
|
+
|
|
15
|
+
type Story = StoryObj<typeof ThemeToggle>
|
|
16
|
+
|
|
17
|
+
export const Default: Story = {
|
|
18
|
+
render: () => (
|
|
19
|
+
<div className="flex items-center gap-3">
|
|
20
|
+
<Small className="opacity-50">Theme</Small>
|
|
21
|
+
|
|
22
|
+
<ThemeToggle />
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const WithHamburger: Story = {
|
|
28
|
+
name: 'Beside Hamburger',
|
|
29
|
+
render: () => {
|
|
30
|
+
const [open, setOpen] = useState(false)
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="flex items-center gap-3">
|
|
34
|
+
<ThemeToggle />
|
|
35
|
+
|
|
36
|
+
<button
|
|
37
|
+
aria-label={open ? 'Close menu' : 'Open menu'}
|
|
38
|
+
className="cursor-pointer bg-transparent p-2"
|
|
39
|
+
onClick={() => setOpen(v => !v)}
|
|
40
|
+
type="button"
|
|
41
|
+
>
|
|
42
|
+
<HamburgerIcon open={open} />
|
|
43
|
+
</button>
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useStore } from '@nanostores/react'
|
|
4
|
+
|
|
5
|
+
import { cn } from '../../utils'
|
|
6
|
+
|
|
7
|
+
import { $lightMode, toggleLens } from './overlays'
|
|
8
|
+
|
|
9
|
+
export function ThemeToggle({ className, style }: ThemeToggleProps) {
|
|
10
|
+
const light = useStore($lightMode)
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<button
|
|
14
|
+
aria-label={light ? 'Switch to dark mode' : 'Switch to light mode'}
|
|
15
|
+
className={cn(
|
|
16
|
+
'relative flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full',
|
|
17
|
+
'border border-current/25 bg-current/8 transition-colors',
|
|
18
|
+
'hover:bg-current/15',
|
|
19
|
+
className
|
|
20
|
+
)}
|
|
21
|
+
onClick={toggleLens}
|
|
22
|
+
style={style}
|
|
23
|
+
type="button"
|
|
24
|
+
>
|
|
25
|
+
<svg
|
|
26
|
+
className="absolute left-1 size-3.5 opacity-40"
|
|
27
|
+
fill="none"
|
|
28
|
+
stroke="currentColor"
|
|
29
|
+
strokeLinecap="round"
|
|
30
|
+
strokeLinejoin="round"
|
|
31
|
+
strokeWidth={2}
|
|
32
|
+
viewBox="0 0 24 24"
|
|
33
|
+
>
|
|
34
|
+
<circle cx={12} cy={12} r={5} />
|
|
35
|
+
|
|
36
|
+
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" />
|
|
37
|
+
</svg>
|
|
38
|
+
|
|
39
|
+
<svg
|
|
40
|
+
className="absolute right-1 size-3.5 opacity-40"
|
|
41
|
+
fill="none"
|
|
42
|
+
stroke="currentColor"
|
|
43
|
+
strokeLinecap="round"
|
|
44
|
+
strokeLinejoin="round"
|
|
45
|
+
strokeWidth={2}
|
|
46
|
+
viewBox="0 0 24 24"
|
|
47
|
+
>
|
|
48
|
+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
|
49
|
+
</svg>
|
|
50
|
+
|
|
51
|
+
<span
|
|
52
|
+
aria-hidden
|
|
53
|
+
className={cn(
|
|
54
|
+
'bg-midground absolute size-4 rounded-full',
|
|
55
|
+
'transition-transform duration-200 ease-out'
|
|
56
|
+
)}
|
|
57
|
+
style={{ transform: `translateX(${light ? 2 : 22}px)` }}
|
|
58
|
+
/>
|
|
59
|
+
</button>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface ThemeToggleProps {
|
|
64
|
+
className?: string
|
|
65
|
+
style?: React.CSSProperties
|
|
66
|
+
}
|