@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,129 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useStore } from '@nanostores/react'
|
|
4
|
+
import { createElement, useMemo } from 'react'
|
|
5
|
+
|
|
6
|
+
import { getControlAtom } from '../../hooks/use-smooth-controls'
|
|
7
|
+
import { cn, type PolyProps, polyRef } from '../../utils'
|
|
8
|
+
import { colorDodge, colorMix } from '../../utils/color'
|
|
9
|
+
|
|
10
|
+
const LAYER_KEYS = { bg: 'bgColor', fg: 'fgColor', mg: 'mgColor' } as const
|
|
11
|
+
type Layer = keyof typeof LAYER_KEYS
|
|
12
|
+
type LayerSpec = `${Layer}/${number}` | Layer
|
|
13
|
+
|
|
14
|
+
const parseSpec = (spec: LayerSpec): [Layer, number?] => {
|
|
15
|
+
const [layer, alpha] = spec.split('/') as [Layer, string?]
|
|
16
|
+
|
|
17
|
+
return [layer, alpha ? parseFloat(alpha) : undefined]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const useControlColor = (key: string, fallback: string) => {
|
|
21
|
+
const atom = getControlAtom<string>('Lens', key)
|
|
22
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
23
|
+
return (atom ? useStore(atom) : undefined) ?? fallback
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const useBlend = (against: Layer, spec?: LayerSpec | string) => {
|
|
27
|
+
const layerKey = spec?.split('/')[0]
|
|
28
|
+
const isLayerSpec = layerKey && layerKey in LAYER_KEYS
|
|
29
|
+
|
|
30
|
+
const [target, alpha] = isLayerSpec
|
|
31
|
+
? parseSpec(spec as LayerSpec)
|
|
32
|
+
: [undefined, undefined]
|
|
33
|
+
|
|
34
|
+
const againstColor = useControlColor(LAYER_KEYS[against], '#041c1c')
|
|
35
|
+
const fgColor = useControlColor(LAYER_KEYS.fg, '#ffe6cb')
|
|
36
|
+
const mgColor = useControlColor(LAYER_KEYS.mg, '#ffe6cb')
|
|
37
|
+
const bgColor = useControlColor(LAYER_KEYS.bg, '#ffe6cb')
|
|
38
|
+
|
|
39
|
+
const targetColor = target
|
|
40
|
+
? target === 'fg'
|
|
41
|
+
? fgColor
|
|
42
|
+
: target === 'mg'
|
|
43
|
+
? mgColor
|
|
44
|
+
: bgColor
|
|
45
|
+
: spec
|
|
46
|
+
|
|
47
|
+
return useMemo(() => {
|
|
48
|
+
if (!spec || !targetColor) {
|
|
49
|
+
return undefined
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const result = colorDodge(againstColor, targetColor)
|
|
53
|
+
|
|
54
|
+
return alpha != null ? colorMix(result, alpha) : result
|
|
55
|
+
}, [spec, againstColor, targetColor, alpha])
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const useBlendMode = (opts: BlendModeOpts = {}): BlendColors => {
|
|
59
|
+
const { against = 'bg', background, color } = opts
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
backgroundColor: useBlend(against, background),
|
|
63
|
+
color: useBlend(against, color)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const withBlendMode = <P extends BlendColors>(
|
|
68
|
+
Component: React.ComponentType<P>,
|
|
69
|
+
opts?: BlendModeOpts
|
|
70
|
+
) => {
|
|
71
|
+
const Wrapped = (
|
|
72
|
+
props: Omit<P, keyof BlendColors> & Partial<BlendModeOpts>
|
|
73
|
+
) => {
|
|
74
|
+
const { against, background, color, ...rest } = props as P & BlendModeOpts
|
|
75
|
+
|
|
76
|
+
const colors = useBlendMode({
|
|
77
|
+
against: against ?? opts?.against,
|
|
78
|
+
background: background ?? opts?.background,
|
|
79
|
+
color: color ?? opts?.color
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
return <Component {...(rest as P)} {...colors} />
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
Wrapped.displayName = `withBlendMode(${Component.displayName ?? Component.name ?? 'Component'})`
|
|
86
|
+
|
|
87
|
+
return Wrapped
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const BlendMode = polyRef<'div', BlendModeOwnProps>(
|
|
91
|
+
(
|
|
92
|
+
{ against, as, background, children, className, color, style, ...rest },
|
|
93
|
+
ref
|
|
94
|
+
) => {
|
|
95
|
+
const colors = useBlendMode({ against, background, color })
|
|
96
|
+
|
|
97
|
+
if (typeof children === 'function') {
|
|
98
|
+
return <>{children(colors)}</>
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return createElement((as ?? 'div') as React.ElementType, {
|
|
102
|
+
...rest,
|
|
103
|
+
children,
|
|
104
|
+
className: cn(className),
|
|
105
|
+
ref,
|
|
106
|
+
style: { ...colors, ...style }
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
interface BlendModeOwnProps extends BlendModeOpts {
|
|
112
|
+
children?: ((colors: BlendColors) => React.ReactNode) | React.ReactNode
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface BlendColors {
|
|
116
|
+
backgroundColor?: string
|
|
117
|
+
color?: string
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface BlendModeOpts {
|
|
121
|
+
against?: Layer
|
|
122
|
+
background?: LayerSpec | string
|
|
123
|
+
color?: LayerSpec | string
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export type BlendModeProps<T extends React.ElementType = 'div'> = PolyProps<
|
|
127
|
+
T,
|
|
128
|
+
BlendModeOwnProps
|
|
129
|
+
>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
|
|
3
|
+
import { Blink } from './blink'
|
|
4
|
+
import { Typography } from './typography'
|
|
5
|
+
import { Small } from './typography/small'
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
component: Blink,
|
|
9
|
+
title: 'Components/Blink'
|
|
10
|
+
} satisfies Meta<typeof Blink>
|
|
11
|
+
|
|
12
|
+
export default meta
|
|
13
|
+
|
|
14
|
+
type Story = StoryObj<typeof meta>
|
|
15
|
+
|
|
16
|
+
export const Cursors: Story = {
|
|
17
|
+
render: () => (
|
|
18
|
+
<div className="group flex flex-col gap-3">
|
|
19
|
+
<Small className="opacity-40">Hover to see the cursors blink</Small>
|
|
20
|
+
|
|
21
|
+
<Typography className="relative" mono>
|
|
22
|
+
Block
|
|
23
|
+
<Blink className="absolute" cursor="block" />
|
|
24
|
+
</Typography>
|
|
25
|
+
|
|
26
|
+
<Typography className="relative" mono>
|
|
27
|
+
Line
|
|
28
|
+
<Blink className="absolute" cursor="line" />
|
|
29
|
+
</Typography>
|
|
30
|
+
</div>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../utils'
|
|
4
|
+
|
|
5
|
+
export function Blink({ className, cursor = 'block' }: BlinkProps) {
|
|
6
|
+
return (
|
|
7
|
+
<span
|
|
8
|
+
className={cn(
|
|
9
|
+
'blink hidden group-hover:inline-block',
|
|
10
|
+
'dither ml-1 w-[1.2ch]',
|
|
11
|
+
cursor === 'block' ? '-mb-[0.15em] h-[1.1em]' : '-mb-[0.1em] h-[2px]',
|
|
12
|
+
className
|
|
13
|
+
)}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface BlinkProps {
|
|
19
|
+
className?: string
|
|
20
|
+
cursor?: 'block' | 'line'
|
|
21
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { expect } from 'storybook/test'
|
|
3
|
+
|
|
4
|
+
import { Button } from './button'
|
|
5
|
+
import { ArrowIcon, LinkIcon, SearchIcon } from './icons'
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof Button> = {
|
|
8
|
+
argTypes: {
|
|
9
|
+
children: { control: 'text' },
|
|
10
|
+
disabled: { control: 'boolean' },
|
|
11
|
+
invert: { control: 'boolean' },
|
|
12
|
+
outlined: { control: 'boolean' }
|
|
13
|
+
},
|
|
14
|
+
args: { children: 'Normal', disabled: false, invert: false, outlined: false },
|
|
15
|
+
component: Button,
|
|
16
|
+
title: 'Components/Button'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default meta
|
|
20
|
+
|
|
21
|
+
type Story = StoryObj<typeof Button>
|
|
22
|
+
|
|
23
|
+
export const Playground: Story = {
|
|
24
|
+
args: { prefix: <ArrowIcon direction="right" /> }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const AllVariants: Story = {
|
|
28
|
+
render: () => (
|
|
29
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
30
|
+
<Button prefix={<ArrowIcon direction="right" />}>Normal</Button>
|
|
31
|
+
<Button invert prefix={<ArrowIcon direction="right" />}>
|
|
32
|
+
Inverted
|
|
33
|
+
</Button>
|
|
34
|
+
<Button outlined prefix={<ArrowIcon direction="right" />}>
|
|
35
|
+
Outlined
|
|
36
|
+
</Button>
|
|
37
|
+
<Button invert outlined prefix={<ArrowIcon direction="right" />}>
|
|
38
|
+
Out + Inv
|
|
39
|
+
</Button>
|
|
40
|
+
</div>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const WithIcons: Story = {
|
|
45
|
+
render: () => (
|
|
46
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
47
|
+
<Button suffix={<LinkIcon />}>Suffix</Button>
|
|
48
|
+
<Button
|
|
49
|
+
prefix={<SearchIcon />}
|
|
50
|
+
suffix={<ArrowIcon direction="right" />}
|
|
51
|
+
>
|
|
52
|
+
Prefix + Suffix
|
|
53
|
+
</Button>
|
|
54
|
+
<Button disabled prefix={<ArrowIcon direction="right" />}>
|
|
55
|
+
Disabled
|
|
56
|
+
</Button>
|
|
57
|
+
</div>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const CssCheck: Story = {
|
|
62
|
+
args: { children: 'Danger', destructive: true },
|
|
63
|
+
play: async ({ canvas }) => {
|
|
64
|
+
const button = canvas.getByRole('button', { name: /danger/i })
|
|
65
|
+
|
|
66
|
+
await expect(getComputedStyle(button).backgroundColor).toBe('rgb(251, 44, 54)')
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
2
|
+
import { cloneElement } from 'react'
|
|
3
|
+
|
|
4
|
+
import { cn } from '../../utils'
|
|
5
|
+
|
|
6
|
+
import { Typography } from './typography'
|
|
7
|
+
|
|
8
|
+
const SHADOW_DEFAULT =
|
|
9
|
+
'shadow-[inset_-1px_-1px_0_0_#00000080,inset_1px_1px_0_0_#ffffff80]'
|
|
10
|
+
const SHADOW_INVERT =
|
|
11
|
+
'shadow-[inset_-1px_-1px_0_0_#00000080,inset_1px_1px_0_0_#ffffff29]'
|
|
12
|
+
const SHADOW_INVERT_OUTLINED =
|
|
13
|
+
'shadow-[inset_-1px_-1px_0_0_#ffffff12,inset_1px_1px_0_0_#ffffff29]'
|
|
14
|
+
const ACTIVE_FILTER =
|
|
15
|
+
'active:[filter:invert(1)_brightness(calc(100-99*var(--foreground-alpha,0)))]'
|
|
16
|
+
|
|
17
|
+
const buttonVariants = cva(
|
|
18
|
+
[
|
|
19
|
+
'group relative grid cursor-pointer grid-cols-[auto_1fr_auto] items-center',
|
|
20
|
+
'text-display leading-0 font-bold tracking-[0.2em]',
|
|
21
|
+
'disabled:pointer-events-none disabled:bg-midground/15 disabled:text-midground disabled:shadow-none'
|
|
22
|
+
],
|
|
23
|
+
{
|
|
24
|
+
compoundVariants: [
|
|
25
|
+
// ── invert × outlined matrix (default surface, no ghost/destructive) ──
|
|
26
|
+
{
|
|
27
|
+
class: `bg-midground text-background-base active:invert ${SHADOW_DEFAULT}`,
|
|
28
|
+
destructive: false,
|
|
29
|
+
ghost: false,
|
|
30
|
+
invert: false,
|
|
31
|
+
outlined: false
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
class: `bg-midground/15 text-midground ${SHADOW_INVERT} ${ACTIVE_FILTER}`,
|
|
35
|
+
destructive: false,
|
|
36
|
+
ghost: false,
|
|
37
|
+
invert: true,
|
|
38
|
+
outlined: false
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
class: `shadow-midground ${SHADOW_DEFAULT} ${ACTIVE_FILTER}`,
|
|
42
|
+
destructive: false,
|
|
43
|
+
ghost: false,
|
|
44
|
+
invert: false,
|
|
45
|
+
outlined: true
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
class: `${SHADOW_INVERT_OUTLINED} ${ACTIVE_FILTER}`,
|
|
49
|
+
destructive: false,
|
|
50
|
+
ghost: false,
|
|
51
|
+
invert: true,
|
|
52
|
+
outlined: true
|
|
53
|
+
},
|
|
54
|
+
// ── ghost: no chrome, hover bg only ──
|
|
55
|
+
{
|
|
56
|
+
class: 'bg-transparent text-current hover:bg-midground/10 shadow-none',
|
|
57
|
+
destructive: false,
|
|
58
|
+
ghost: true
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
class:
|
|
62
|
+
'bg-transparent text-destructive hover:bg-destructive/10 shadow-none',
|
|
63
|
+
destructive: true,
|
|
64
|
+
ghost: true
|
|
65
|
+
},
|
|
66
|
+
// ── solid destructive ──
|
|
67
|
+
{
|
|
68
|
+
class: `bg-destructive text-destructive-foreground hover:bg-destructive/90 ${SHADOW_INVERT}`,
|
|
69
|
+
destructive: true,
|
|
70
|
+
ghost: false,
|
|
71
|
+
outlined: false
|
|
72
|
+
},
|
|
73
|
+
// ── outlined destructive ──
|
|
74
|
+
{
|
|
75
|
+
class:
|
|
76
|
+
'border border-destructive/40 bg-transparent text-destructive hover:bg-destructive/10 shadow-none',
|
|
77
|
+
destructive: true,
|
|
78
|
+
ghost: false,
|
|
79
|
+
outlined: true
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
defaultVariants: {
|
|
83
|
+
destructive: false,
|
|
84
|
+
ghost: false,
|
|
85
|
+
invert: false,
|
|
86
|
+
outlined: false,
|
|
87
|
+
size: 'default'
|
|
88
|
+
},
|
|
89
|
+
variants: {
|
|
90
|
+
destructive: { true: '' },
|
|
91
|
+
ghost: { true: '' },
|
|
92
|
+
invert: { true: '' },
|
|
93
|
+
outlined: { true: 'text-midground bg-transparent' },
|
|
94
|
+
size: {
|
|
95
|
+
default: 'px-[.9em_.75em] py-[1.25em]',
|
|
96
|
+
icon: 'p-2 aspect-square grid-cols-1 place-items-center [&>svg]:size-3.5',
|
|
97
|
+
sm: 'px-3 py-1.5 text-[0.7rem] tracking-[0.15em] [&>svg]:size-3',
|
|
98
|
+
xs: 'p-1 aspect-square grid-cols-1 place-items-center [&>svg]:size-3'
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
const IconSlot = ({
|
|
105
|
+
icon,
|
|
106
|
+
side
|
|
107
|
+
}: {
|
|
108
|
+
icon: React.ReactNode
|
|
109
|
+
side: 'left' | 'right'
|
|
110
|
+
}) => (
|
|
111
|
+
<>
|
|
112
|
+
<span className="w-5" />
|
|
113
|
+
|
|
114
|
+
<span
|
|
115
|
+
className={cn(
|
|
116
|
+
'absolute top-1/2 -translate-y-1/2',
|
|
117
|
+
side === 'left' ? 'left-3' : 'right-3'
|
|
118
|
+
)}
|
|
119
|
+
>
|
|
120
|
+
{typeof icon === 'object'
|
|
121
|
+
? cloneElement(icon as React.ReactElement<any>, {
|
|
122
|
+
className: 'size-3.5'
|
|
123
|
+
})
|
|
124
|
+
: icon}
|
|
125
|
+
</span>
|
|
126
|
+
</>
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
export const Button = ({
|
|
130
|
+
children,
|
|
131
|
+
className,
|
|
132
|
+
destructive,
|
|
133
|
+
ghost,
|
|
134
|
+
invert,
|
|
135
|
+
outlined,
|
|
136
|
+
prefix,
|
|
137
|
+
size,
|
|
138
|
+
suffix,
|
|
139
|
+
...props
|
|
140
|
+
}: ButtonProps) => (
|
|
141
|
+
<Typography
|
|
142
|
+
as="button"
|
|
143
|
+
className={cn(
|
|
144
|
+
buttonVariants({ destructive, ghost, invert, outlined, size }),
|
|
145
|
+
className
|
|
146
|
+
)}
|
|
147
|
+
mono
|
|
148
|
+
{...props}
|
|
149
|
+
>
|
|
150
|
+
{!ghost && (
|
|
151
|
+
<span
|
|
152
|
+
aria-hidden
|
|
153
|
+
className="arc-border opacity-0 transition-opacity duration-200 group-hover:opacity-100 group-focus-visible:opacity-100 group-active:opacity-100"
|
|
154
|
+
/>
|
|
155
|
+
)}
|
|
156
|
+
{prefix && <IconSlot icon={prefix} side="left" />}
|
|
157
|
+
{children}
|
|
158
|
+
{suffix && <IconSlot icon={suffix} side="right" />}
|
|
159
|
+
</Typography>
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
interface ButtonProps
|
|
163
|
+
extends Omit<
|
|
164
|
+
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
165
|
+
'prefix' | 'suffix'
|
|
166
|
+
>,
|
|
167
|
+
VariantProps<typeof buttonVariants> {
|
|
168
|
+
prefix?: React.ReactNode
|
|
169
|
+
suffix?: React.ReactNode
|
|
170
|
+
}
|
|
@@ -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/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 * as CheckboxPrimitive from '@radix-ui/react-checkbox'
|
|
4
|
+
import { forwardRef, type ComponentPropsWithoutRef, type ElementRef } from 'react'
|
|
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/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
|
+
}
|