@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.
Files changed (216) hide show
  1. package/CHANGELOG.md +227 -0
  2. package/README.md +24 -4
  3. package/dist/fonts.js +1 -0
  4. package/dist/hooks/use-capped-frame.js +1 -0
  5. package/dist/hooks/use-css-var-dims.js +1 -0
  6. package/dist/hooks/use-gpu-tier.js +1 -0
  7. package/dist/hooks/use-render-loop.js +1 -0
  8. package/dist/hooks/use-smooth-controls.js +1 -0
  9. package/dist/index.js +1 -0
  10. package/dist/ui/basic-page.js +1 -0
  11. package/dist/ui/components/animated-count.js +1 -0
  12. package/dist/ui/components/ascii.js +1 -0
  13. package/dist/ui/components/badge.js +2 -1
  14. package/dist/ui/components/badges/nous-girl.js +1 -0
  15. package/dist/ui/components/blend-mode.js +1 -0
  16. package/dist/ui/components/blink.js +1 -0
  17. package/dist/ui/components/button.js +2 -1
  18. package/dist/ui/components/checkbox.js +1 -0
  19. package/dist/ui/components/command-block.js +4 -3
  20. package/dist/ui/components/cursor.js +1 -0
  21. package/dist/ui/components/dropdown-menu.js +1 -0
  22. package/dist/ui/components/fit-text/index.js +1 -0
  23. package/dist/ui/components/graphs/bar-chart.js +1 -0
  24. package/dist/ui/components/graphs/index.js +1 -0
  25. package/dist/ui/components/graphs/line-chart.js +1 -0
  26. package/dist/ui/components/graphs/utils.js +1 -0
  27. package/dist/ui/components/grid/index.js +1 -0
  28. package/dist/ui/components/hover-bg.js +1 -0
  29. package/dist/ui/components/icons/arrow.js +1 -0
  30. package/dist/ui/components/icons/check.js +1 -0
  31. package/dist/ui/components/icons/chevron.js +1 -0
  32. package/dist/ui/components/icons/discord.js +1 -0
  33. package/dist/ui/components/icons/eye.js +1 -0
  34. package/dist/ui/components/icons/gear.js +1 -0
  35. package/dist/ui/components/icons/github.js +1 -0
  36. package/dist/ui/components/icons/hamburger.js +1 -0
  37. package/dist/ui/components/icons/heart.js +1 -0
  38. package/dist/ui/components/icons/index.js +1 -0
  39. package/dist/ui/components/icons/link.js +1 -0
  40. package/dist/ui/components/icons/minus.js +1 -0
  41. package/dist/ui/components/icons/search.js +1 -0
  42. package/dist/ui/components/image-distortion.js +1 -0
  43. package/dist/ui/components/leva-client.js +1 -0
  44. package/dist/ui/components/list-item.js +3 -2
  45. package/dist/ui/components/modal/index.js +1 -0
  46. package/dist/ui/components/modal/modal.css +1 -1
  47. package/dist/ui/components/overlays/blend-modes.js +1 -0
  48. package/dist/ui/components/overlays/glitch.js +1 -0
  49. package/dist/ui/components/overlays/greys.js +1 -0
  50. package/dist/ui/components/overlays/index.js +1 -0
  51. package/dist/ui/components/overlays/lens-layers.js +1 -0
  52. package/dist/ui/components/overlays/lens.js +1 -0
  53. package/dist/ui/components/overlays/noise.js +1 -0
  54. package/dist/ui/components/overlays/vignette.js +1 -0
  55. package/dist/ui/components/poster.js +1 -0
  56. package/dist/ui/components/progress.js +1 -0
  57. package/dist/ui/components/scene-canvas.js +1 -0
  58. package/dist/ui/components/scramble.js +1 -0
  59. package/dist/ui/components/segmented.js +5 -4
  60. package/dist/ui/components/select.js +1 -0
  61. package/dist/ui/components/selection-switcher.js +1 -0
  62. package/dist/ui/components/shader.js +1 -0
  63. package/dist/ui/components/socials.js +1 -0
  64. package/dist/ui/components/spinner.js +1 -0
  65. package/dist/ui/components/stats.js +2 -1
  66. package/dist/ui/components/switch.js +1 -0
  67. package/dist/ui/components/tabs.js +4 -3
  68. package/dist/ui/components/terminal-demo.js +2 -1
  69. package/dist/ui/components/theme-toggle.js +1 -0
  70. package/dist/ui/components/tier-card.js +2 -1
  71. package/dist/ui/components/tv.js +1 -0
  72. package/dist/ui/components/typography/h1.js +1 -0
  73. package/dist/ui/components/typography/h2.js +1 -0
  74. package/dist/ui/components/typography/index.js +1 -0
  75. package/dist/ui/components/typography/legend.js +1 -0
  76. package/dist/ui/components/typography/small.js +1 -0
  77. package/dist/ui/components/watchlist.js +2 -1
  78. package/dist/ui/footer.js +1 -0
  79. package/dist/ui/globals.css +33 -1
  80. package/dist/ui/header.js +1 -0
  81. package/dist/ui/layout-wrapper.js +2 -1
  82. package/dist/utils/color.js +1 -0
  83. package/dist/utils/index.js +1 -0
  84. package/dist/utils/poly.js +1 -0
  85. package/package.json +4 -2
  86. package/src/assets/filler-bg0.webp +0 -0
  87. package/src/assets.d.ts +38 -0
  88. package/src/fonts/Collapse-Bold.woff2 +0 -0
  89. package/src/fonts/Collapse-BoldItalic.woff2 +0 -0
  90. package/src/fonts/Collapse-Italic.woff2 +0 -0
  91. package/src/fonts/Collapse-Light.woff2 +0 -0
  92. package/src/fonts/Collapse-LightItalic.woff2 +0 -0
  93. package/src/fonts/Collapse-Regular.woff2 +0 -0
  94. package/src/fonts/Collapse-Thin.woff2 +0 -0
  95. package/src/fonts/Collapse-ThinItalic.woff2 +0 -0
  96. package/src/fonts/Mondwest-Regular.woff2 +0 -0
  97. package/src/fonts/Neuebit-Bold.woff2 +0 -0
  98. package/src/fonts/RulesCompressed-Medium.woff2 +0 -0
  99. package/src/fonts/RulesCompressed-Regular.woff2 +0 -0
  100. package/src/fonts/RulesExpanded-Bold.woff2 +0 -0
  101. package/src/fonts/RulesExpanded-Regular.woff2 +0 -0
  102. package/src/fonts.ts +6 -0
  103. package/src/hooks/use-capped-frame.ts +18 -0
  104. package/src/hooks/use-css-var-dims.ts +39 -0
  105. package/src/hooks/use-gpu-tier.ts +165 -0
  106. package/src/hooks/use-render-loop.ts +121 -0
  107. package/src/hooks/use-smooth-controls.ts +318 -0
  108. package/src/index.ts +109 -0
  109. package/src/ui/basic-page.tsx +34 -0
  110. package/src/ui/build.css +4 -0
  111. package/src/ui/components/animated-count.stories.tsx +67 -0
  112. package/src/ui/components/animated-count.tsx +168 -0
  113. package/src/ui/components/ascii.stories.tsx +30 -0
  114. package/src/ui/components/ascii.tsx +110 -0
  115. package/src/ui/components/badge.stories.tsx +31 -0
  116. package/src/ui/components/badge.tsx +60 -0
  117. package/src/ui/components/badges/nous-girl.tsx +52 -0
  118. package/src/ui/components/blend-mode.stories.tsx +33 -0
  119. package/src/ui/components/blend-mode.tsx +129 -0
  120. package/src/ui/components/blink.stories.tsx +32 -0
  121. package/src/ui/components/blink.tsx +21 -0
  122. package/src/ui/components/button.stories.tsx +68 -0
  123. package/src/ui/components/button.tsx +170 -0
  124. package/src/ui/components/checkbox.stories.tsx +113 -0
  125. package/src/ui/components/checkbox.tsx +36 -0
  126. package/src/ui/components/command-block.stories.tsx +52 -0
  127. package/src/ui/components/command-block.tsx +86 -0
  128. package/src/ui/components/cursor.tsx +115 -0
  129. package/src/ui/components/dropdown-menu.stories.tsx +52 -0
  130. package/src/ui/components/dropdown-menu.tsx +117 -0
  131. package/src/ui/components/fit-text/fit-text.css +42 -0
  132. package/src/ui/components/fit-text/index.stories.tsx +33 -0
  133. package/src/ui/components/fit-text/index.tsx +45 -0
  134. package/src/ui/components/graphs/bar-chart.tsx +153 -0
  135. package/src/ui/components/graphs/index.stories.tsx +64 -0
  136. package/src/ui/components/graphs/index.tsx +4 -0
  137. package/src/ui/components/graphs/line-chart.tsx +213 -0
  138. package/src/ui/components/graphs/utils.tsx +265 -0
  139. package/src/ui/components/grid/grid.css +79 -0
  140. package/src/ui/components/grid/index.tsx +19 -0
  141. package/src/ui/components/hover-bg.stories.tsx +29 -0
  142. package/src/ui/components/hover-bg.tsx +15 -0
  143. package/src/ui/components/icons/arrow.tsx +42 -0
  144. package/src/ui/components/icons/check.tsx +14 -0
  145. package/src/ui/components/icons/chevron.tsx +45 -0
  146. package/src/ui/components/icons/discord.tsx +16 -0
  147. package/src/ui/components/icons/eye.tsx +12 -0
  148. package/src/ui/components/icons/gear.tsx +51 -0
  149. package/src/ui/components/icons/github.tsx +16 -0
  150. package/src/ui/components/icons/hamburger.tsx +52 -0
  151. package/src/ui/components/icons/heart.tsx +12 -0
  152. package/src/ui/components/icons/index.ts +12 -0
  153. package/src/ui/components/icons/link.tsx +14 -0
  154. package/src/ui/components/icons/minus.tsx +14 -0
  155. package/src/ui/components/icons/search.tsx +28 -0
  156. package/src/ui/components/image-distortion.stories.tsx +120 -0
  157. package/src/ui/components/image-distortion.tsx +498 -0
  158. package/src/ui/components/leva-client.tsx +14 -0
  159. package/src/ui/components/list-item.stories.tsx +83 -0
  160. package/src/ui/components/list-item.tsx +37 -0
  161. package/src/ui/components/modal/index.stories.tsx +46 -0
  162. package/src/ui/components/modal/index.tsx +48 -0
  163. package/src/ui/components/modal/modal.css +36 -0
  164. package/src/ui/components/overlays/blend-modes.ts +13 -0
  165. package/src/ui/components/overlays/glitch.tsx +243 -0
  166. package/src/ui/components/overlays/greys.tsx +386 -0
  167. package/src/ui/components/overlays/index.tsx +47 -0
  168. package/src/ui/components/overlays/lens-layers.tsx +119 -0
  169. package/src/ui/components/overlays/lens.ts +91 -0
  170. package/src/ui/components/overlays/noise.tsx +174 -0
  171. package/src/ui/components/overlays/vignette.tsx +60 -0
  172. package/src/ui/components/poster.stories.tsx +513 -0
  173. package/src/ui/components/poster.tsx +411 -0
  174. package/src/ui/components/progress.stories.tsx +48 -0
  175. package/src/ui/components/progress.tsx +56 -0
  176. package/src/ui/components/scene-canvas.tsx +254 -0
  177. package/src/ui/components/scramble.stories.tsx +49 -0
  178. package/src/ui/components/scramble.tsx +95 -0
  179. package/src/ui/components/segmented.stories.tsx +101 -0
  180. package/src/ui/components/segmented.tsx +81 -0
  181. package/src/ui/components/select.stories.tsx +88 -0
  182. package/src/ui/components/select.tsx +267 -0
  183. package/src/ui/components/selection-switcher.tsx +44 -0
  184. package/src/ui/components/shader.tsx +83 -0
  185. package/src/ui/components/socials.tsx +42 -0
  186. package/src/ui/components/spinner.stories.tsx +101 -0
  187. package/src/ui/components/spinner.tsx +60 -0
  188. package/src/ui/components/stats.stories.tsx +24 -0
  189. package/src/ui/components/stats.tsx +53 -0
  190. package/src/ui/components/switch.stories.tsx +77 -0
  191. package/src/ui/components/switch.tsx +48 -0
  192. package/src/ui/components/tabs.stories.tsx +101 -0
  193. package/src/ui/components/tabs.tsx +66 -0
  194. package/src/ui/components/terminal-demo.stories.tsx +67 -0
  195. package/src/ui/components/terminal-demo.tsx +189 -0
  196. package/src/ui/components/theme-toggle.stories.tsx +47 -0
  197. package/src/ui/components/theme-toggle.tsx +66 -0
  198. package/src/ui/components/tier-card.stories.tsx +217 -0
  199. package/src/ui/components/tier-card.tsx +190 -0
  200. package/src/ui/components/tv.stories.tsx +37 -0
  201. package/src/ui/components/tv.tsx +257 -0
  202. package/src/ui/components/typography/h1.tsx +18 -0
  203. package/src/ui/components/typography/h2.tsx +18 -0
  204. package/src/ui/components/typography/index.tsx +54 -0
  205. package/src/ui/components/typography/legend.tsx +24 -0
  206. package/src/ui/components/typography/small.tsx +11 -0
  207. package/src/ui/components/watchlist.stories.tsx +33 -0
  208. package/src/ui/components/watchlist.tsx +105 -0
  209. package/src/ui/fonts.css +63 -0
  210. package/src/ui/footer.tsx +111 -0
  211. package/src/ui/globals.css +383 -0
  212. package/src/ui/header.tsx +398 -0
  213. package/src/ui/layout-wrapper.tsx +11 -0
  214. package/src/utils/color.ts +21 -0
  215. package/src/utils/index.ts +62 -0
  216. 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 &quot;hello world&quot;
47
+ </span>
48
+
49
+ <CopyButton text="echo 'hello world'" />
50
+ </div>
51
+ )
52
+ }