@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,18 @@
1
+ import { forwardRef } from 'react'
2
+
3
+ import { cn } from '../../../utils'
4
+
5
+ import { Typography, type TypographyProps } from '.'
6
+
7
+ export const H1 = forwardRef<HTMLHeadingElement, TypographyProps<'h1'>>(
8
+ ({ className, ...props }, ref) => {
9
+ return (
10
+ <Typography
11
+ as="h1"
12
+ className={cn('font-bold', className)}
13
+ variant="xl"
14
+ {...{ ref, ...props }}
15
+ />
16
+ )
17
+ }
18
+ )
@@ -0,0 +1,18 @@
1
+ import { forwardRef } from 'react'
2
+
3
+ import { cn } from '../../../utils'
4
+
5
+ import { Typography, type TypographyProps } from '.'
6
+
7
+ export const H2 = forwardRef<HTMLHeadingElement, TypographyProps<'h2'>>(
8
+ ({ className, ...props }, ref) => {
9
+ return (
10
+ <Typography
11
+ as="h2"
12
+ className={cn('font-bold', className)}
13
+ variant="lg"
14
+ {...{ ref, ...props }}
15
+ />
16
+ )
17
+ }
18
+ )
@@ -0,0 +1,54 @@
1
+ import { cva, type VariantProps } from 'class-variance-authority'
2
+ import { createElement } from 'react'
3
+
4
+ import { cn, type PolyProps, polyRef } from '../../../utils'
5
+
6
+ const typographyVariants = cva('font-sans', {
7
+ variants: {
8
+ compressed: { true: 'font-compressed' },
9
+ courier: { true: 'font-courier' },
10
+ expanded: { true: 'font-expanded' },
11
+ mondwest: { true: 'font-mondwest tracking-[0.1875rem]' },
12
+ mono: { true: 'font-mono' },
13
+ sans: { true: 'font-sans' },
14
+ variant: {
15
+ lg: 'text-[2.625rem] leading-[1] tracking-[0.0525rem]',
16
+ md: 'text-[2.625rem] leading-[1] tracking-[0.0525rem]',
17
+ sm: 'leading-1.4 text-[.9375rem] tracking-[0.1875rem]',
18
+ xl: 'text-[4.5rem] leading-[1] tracking-[0.135rem]'
19
+ }
20
+ }
21
+ })
22
+
23
+ export const Typography = polyRef<'span', OwnProps>(
24
+ (
25
+ {
26
+ as,
27
+ className,
28
+ compressed,
29
+ courier,
30
+ expanded,
31
+ mondwest,
32
+ mono,
33
+ variant,
34
+ ...rest
35
+ },
36
+ ref
37
+ ) => {
38
+ const fonts = { compressed, courier, expanded, mondwest, mono }
39
+ const fontVariant = { ...fonts, sans: !Object.values(fonts).some(Boolean) }
40
+
41
+ return createElement((as ?? 'span') as React.ElementType, {
42
+ ...rest,
43
+ className: cn(typographyVariants({ ...fontVariant, variant }), className),
44
+ ref
45
+ })
46
+ }
47
+ )
48
+
49
+ type OwnProps = VariantProps<typeof typographyVariants>
50
+
51
+ export type TypographyProps<T extends React.ElementType = 'span'> = PolyProps<
52
+ T,
53
+ OwnProps
54
+ >
@@ -0,0 +1,24 @@
1
+ import { cn } from '../../../utils'
2
+
3
+ import { Small } from './small'
4
+
5
+ export function Legend({
6
+ children,
7
+ className,
8
+ label,
9
+ sub,
10
+ ...props
11
+ }: LegendProps) {
12
+ return (
13
+ <hgroup className={cn('flex flex-col gap-2', className)} {...props}>
14
+ <Small>{label}</Small>
15
+ {sub && <Small className="opacity-50">- {sub}</Small>}
16
+ {children}
17
+ </hgroup>
18
+ )
19
+ }
20
+
21
+ interface LegendProps extends React.ComponentProps<'hgroup'> {
22
+ label: React.ReactNode
23
+ sub?: React.ReactNode
24
+ }
@@ -0,0 +1,11 @@
1
+ import { forwardRef } from 'react'
2
+
3
+ import { Typography, type TypographyProps } from '.'
4
+
5
+ export const Small = forwardRef<HTMLSpanElement, TypographyProps<any>>(
6
+ (props, ref) => {
7
+ return (
8
+ <Typography as="small" mondwest variant="sm" {...{ ref, ...props }} />
9
+ )
10
+ }
11
+ )
@@ -0,0 +1,33 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+
3
+ import { Watchlist } from './watchlist'
4
+
5
+ const ADDRESSES = [
6
+ ['0x7a16fF8270133F063aAb6C9977183D9e72835428', '0.50%'],
7
+ ['0xd4e96eF8eEE8678dBFF4d535E015d1a77e7Cc62c', '1.20%'],
8
+ ['0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF', '3.00%'],
9
+ ['0x8ba1f109551bD432803012645Ac136ddd64DBA72', '4.10%']
10
+ ].map(([label, right]) => ({ label, right, url: '#' }))
11
+
12
+ const meta = {
13
+ component: Watchlist,
14
+ title: 'Components/Watchlist'
15
+ } satisfies Meta<typeof Watchlist>
16
+
17
+ export default meta
18
+
19
+ type Story = StoryObj<typeof meta>
20
+
21
+ export const WithCounterAndScramble: Story = {
22
+ args: { counter: true, items: ADDRESSES, scramble: true }
23
+ }
24
+
25
+ export const Simple: Story = {
26
+ args: {
27
+ items: [
28
+ { label: 'One', right: '11%' },
29
+ { label: 'Two', right: '22%' },
30
+ { label: 'Three', right: '33%' }
31
+ ]
32
+ }
33
+ }
@@ -0,0 +1,105 @@
1
+ 'use client'
2
+
3
+ import { cn } from '../../utils'
4
+
5
+ import { Scramble } from './ascii'
6
+ import { LinkIcon } from './icons'
7
+ import { Typography } from './typography'
8
+
9
+ const ETH_RE = /^0x[a-fA-F0-9]{40}$/
10
+ const truncate = (a: string) => `${a.slice(0, 6)}${'·'.repeat(8)}${a.slice(-4)}`
11
+
12
+ export function Watchlist({
13
+ className,
14
+ counter = false,
15
+ items,
16
+ scramble = false,
17
+ ...props
18
+ }: WatchlistProps) {
19
+ return (
20
+ <div className={cn('flex flex-col gap-3', className)} {...props}>
21
+ {items.map(({ label, right, url }, i) => {
22
+ const isStr = typeof label === 'string'
23
+ const eth = isStr && ETH_RE.test(label)
24
+ const text = eth ? truncate(label) : (label as string)
25
+
26
+ return (
27
+ <a
28
+ className={cn(
29
+ 'grid items-center gap-2.5 px-2.5 py-1.5',
30
+ 'text-display leading-[1.4]',
31
+ 'hover:bg-midground/10! hover:ring-2 hover:ring-current/20',
32
+ 'transition-all duration-500 hover:duration-0',
33
+ 'opacity-(--midground-alpha)'
34
+ )}
35
+ href={url}
36
+ key={i}
37
+ rel="noopener noreferrer"
38
+ style={{
39
+ background: `color-mix(in oklch, var(--color-midground) ${10 * Math.max(0, 1 - i / 9)}%, transparent)`,
40
+ gridTemplateColumns: [
41
+ counter && 'auto auto',
42
+ '1fr',
43
+ right && 'auto',
44
+ url && 'auto auto'
45
+ ]
46
+ .filter(Boolean)
47
+ .join(' ')
48
+ }}
49
+ target="_blank"
50
+ >
51
+ {counter && (
52
+ <>
53
+ <Typography
54
+ className="text-lg tracking-[0.35em] opacity-40"
55
+ compressed
56
+ >
57
+ {String(i + 1).padStart(2, '0')}
58
+ </Typography>
59
+
60
+ <span className="text-[0.8125rem] font-bold tracking-[0.4em] opacity-20">
61
+ :
62
+ </span>
63
+ </>
64
+ )}
65
+
66
+ {isStr ? (
67
+ <Typography
68
+ className="min-w-0 overflow-hidden text-lg font-bold tracking-[0.35em]"
69
+ {...(eth ? { mono: true } : { compressed: true })}
70
+ >
71
+ {scramble ? <Scramble delay={i * 80} text={text} /> : text}
72
+ </Typography>
73
+ ) : (
74
+ label
75
+ )}
76
+
77
+ {right && (
78
+ <Typography
79
+ className="text-right text-sm tracking-widest opacity-40"
80
+ mono
81
+ >
82
+ {right}
83
+ </Typography>
84
+ )}
85
+
86
+ {url && (
87
+ <>
88
+ <span className="text-[0.8125rem] tracking-[0.4em] opacity-20">
89
+ :
90
+ </span>
91
+ <LinkIcon className="text-midground size-3.5" />
92
+ </>
93
+ )}
94
+ </a>
95
+ )
96
+ })}
97
+ </div>
98
+ )
99
+ }
100
+
101
+ interface WatchlistProps extends React.ComponentProps<'div'> {
102
+ counter?: boolean
103
+ items: { label?: React.ReactNode; right?: React.ReactNode; url?: string }[]
104
+ scramble?: boolean
105
+ }
@@ -0,0 +1,63 @@
1
+ @font-face {
2
+ font-family: 'Collapse';
3
+ font-style: normal;
4
+ font-weight: 400;
5
+ font-display: swap;
6
+ src: url('../fonts/Collapse-Regular.woff2') format('woff2');
7
+ }
8
+
9
+ @font-face {
10
+ font-family: 'Collapse';
11
+ font-style: normal;
12
+ font-weight: 700;
13
+ font-display: swap;
14
+ src: url('../fonts/Collapse-Bold.woff2') format('woff2');
15
+ }
16
+
17
+ @font-face {
18
+ font-family: 'Rules Compressed';
19
+ font-style: normal;
20
+ font-weight: 400;
21
+ font-display: swap;
22
+ src: url('../fonts/RulesCompressed-Regular.woff2') format('woff2');
23
+ }
24
+
25
+ @font-face {
26
+ font-family: 'Rules Compressed';
27
+ font-style: normal;
28
+ font-weight: 600;
29
+ font-display: swap;
30
+ src: url('../fonts/RulesCompressed-Medium.woff2') format('woff2');
31
+ }
32
+
33
+ @font-face {
34
+ font-family: 'Rules Expanded';
35
+ font-style: normal;
36
+ font-weight: 400;
37
+ font-display: swap;
38
+ src: url('../fonts/RulesExpanded-Regular.woff2') format('woff2');
39
+ }
40
+
41
+ @font-face {
42
+ font-family: 'Rules Expanded';
43
+ font-style: normal;
44
+ font-weight: 700;
45
+ font-display: swap;
46
+ src: url('../fonts/RulesExpanded-Bold.woff2') format('woff2');
47
+ }
48
+
49
+ @font-face {
50
+ font-family: 'Mondwest';
51
+ font-style: normal;
52
+ font-weight: 400;
53
+ font-display: swap;
54
+ src: url('../fonts/Mondwest-Regular.woff2') format('woff2');
55
+ }
56
+
57
+ :root {
58
+ --font-sans: 'Collapse', sans-serif;
59
+ --font-mono: 'Courier Prime', monospace;
60
+ --font-rules-compressed: 'Rules Compressed', sans-serif;
61
+ --font-rules-expanded: 'Rules Expanded', sans-serif;
62
+ --font-mondwest: 'Mondwest', sans-serif;
63
+ }
@@ -0,0 +1,111 @@
1
+ 'use client'
2
+
3
+ import { useRef } from 'react'
4
+
5
+ import { useCssVarDims } from '../hooks/use-css-var-dims'
6
+ import { Cell, Grid } from './components/grid'
7
+ import { Socials, type SocialLink } from './components/socials'
8
+ import { ThemeToggle } from './components/theme-toggle'
9
+ import { Small } from './components/typography/small'
10
+
11
+ const DEFAULT_GROUPS: FooterGroup[] = [
12
+ { label: 'Product', links: ['Overview', 'Features', 'Pricing'] },
13
+ { label: 'Resources', links: ['Docs', 'Blog', 'Support'] },
14
+ { label: 'Company', links: ['About', 'Careers', 'Contact'] },
15
+ { label: 'Legal', links: ['Privacy', 'Terms', 'License'] }
16
+ ]
17
+
18
+ export function Footer({
19
+ className,
20
+ groups = DEFAULT_GROUPS,
21
+ LinkComponent = 'a',
22
+ socials,
23
+ socialsLabel = 'Socials',
24
+ style,
25
+ themeLabel = 'Theme',
26
+ themeToggle = false
27
+ }: FooterProps) {
28
+ const ref = useRef<HTMLElement>(null)
29
+ useCssVarDims('footer', ref)
30
+
31
+ const hasSocials = (socials?.length ?? 0) > 0
32
+ const hasChrome = hasSocials || themeToggle
33
+
34
+ return (
35
+ <footer className={className} ref={ref} style={style}>
36
+ <Grid>
37
+ <Cell>
38
+ <Small className="opacity-50">&copy;{new Date().getFullYear()}</Small>
39
+ </Cell>
40
+
41
+ {groups.map(({ label, links }) => (
42
+ <Cell key={label}>
43
+ <Small className="opacity-50">{label}</Small>
44
+
45
+ <nav className="mt-3 flex flex-col gap-2">
46
+ {links.map(link => {
47
+ const href = typeof link === 'string'
48
+ ? `/${link.toLowerCase()}`
49
+ : link.href
50
+
51
+ const label = typeof link === 'string' ? link : link.label
52
+
53
+ return (
54
+ <Small
55
+ as={LinkComponent}
56
+ className="underline"
57
+ href={href}
58
+ key={label}
59
+ >
60
+ {label}
61
+ </Small>
62
+ )
63
+ })}
64
+ </nav>
65
+ </Cell>
66
+ ))}
67
+ </Grid>
68
+
69
+ {hasChrome && (
70
+ <Grid>
71
+ {hasSocials && (
72
+ <Cell className="flex items-start justify-between">
73
+ <Small className="opacity-50">{socialsLabel}</Small>
74
+
75
+ <Socials items={socials!} />
76
+ </Cell>
77
+ )}
78
+
79
+ {themeToggle && (
80
+ <Cell className="flex items-start justify-between">
81
+ <Small className="opacity-50">{themeLabel}</Small>
82
+
83
+ <ThemeToggle />
84
+ </Cell>
85
+ )}
86
+ </Grid>
87
+ )}
88
+ </footer>
89
+ )
90
+ }
91
+
92
+ export interface FooterGroup {
93
+ label: string
94
+ links: (FooterLink | string)[]
95
+ }
96
+
97
+ export interface FooterLink {
98
+ href: string
99
+ label: string
100
+ }
101
+
102
+ export interface FooterProps {
103
+ className?: string
104
+ groups?: FooterGroup[]
105
+ LinkComponent?: React.ElementType
106
+ socials?: SocialLink[]
107
+ socialsLabel?: string
108
+ style?: React.CSSProperties
109
+ themeLabel?: string
110
+ themeToggle?: boolean
111
+ }