@nous-research/ui 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +266 -0
- package/README.md +24 -4
- package/dist/fonts.js +1 -0
- package/dist/hooks/use-below-breakpoint.d.ts +2 -0
- package/dist/hooks/use-below-breakpoint.js +17 -0
- package/dist/hooks/use-capped-frame.js +1 -0
- package/dist/hooks/use-confirm-delete.d.ts +10 -0
- package/dist/hooks/use-confirm-delete.js +35 -0
- package/dist/hooks/use-css-var-dims.js +1 -0
- package/dist/hooks/use-gpu-tier.js +1 -0
- package/dist/hooks/use-render-loop.js +1 -0
- package/dist/hooks/use-smooth-controls.js +1 -0
- package/dist/hooks/use-toast.d.ts +7 -0
- package/dist/hooks/use-toast.js +21 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.js +23 -1
- package/dist/ui/basic-page.js +1 -0
- package/dist/ui/components/animated-count.js +1 -0
- package/dist/ui/components/ascii.js +1 -0
- package/dist/ui/components/badge.js +2 -1
- package/dist/ui/components/badges/nous-girl.js +1 -0
- package/dist/ui/components/blend-mode.js +1 -0
- package/dist/ui/components/blink.js +1 -0
- package/dist/ui/components/bottom-sheet.d.ts +15 -0
- package/dist/ui/components/bottom-sheet.js +192 -0
- package/dist/ui/components/button.js +2 -1
- package/dist/ui/components/card.d.ts +5 -0
- package/dist/ui/components/card.js +74 -0
- package/dist/ui/components/checkbox.d.ts +1 -1
- package/dist/ui/components/checkbox.js +2 -1
- package/dist/ui/components/command-block.js +4 -3
- package/dist/ui/components/confirm-dialog.d.ts +13 -0
- package/dist/ui/components/confirm-dialog.js +113 -0
- package/dist/ui/components/cursor.js +1 -0
- package/dist/ui/components/dialog.d.ts +15 -0
- package/dist/ui/components/dialog.js +171 -0
- package/dist/ui/components/dropdown-menu.js +1 -0
- package/dist/ui/components/fit-text/index.js +1 -0
- package/dist/ui/components/graphs/bar-chart.js +1 -0
- package/dist/ui/components/graphs/index.js +1 -0
- package/dist/ui/components/graphs/line-chart.js +1 -0
- package/dist/ui/components/graphs/utils.js +1 -0
- package/dist/ui/components/grid/index.js +1 -0
- package/dist/ui/components/hover-bg.js +1 -0
- package/dist/ui/components/icons/arrow.js +1 -0
- package/dist/ui/components/icons/check.js +1 -0
- package/dist/ui/components/icons/chevron.js +1 -0
- package/dist/ui/components/icons/discord.js +1 -0
- package/dist/ui/components/icons/eye.js +1 -0
- package/dist/ui/components/icons/gear.js +1 -0
- package/dist/ui/components/icons/github.js +1 -0
- package/dist/ui/components/icons/hamburger.js +1 -0
- package/dist/ui/components/icons/heart.js +1 -0
- package/dist/ui/components/icons/index.js +1 -0
- package/dist/ui/components/icons/link.js +1 -0
- package/dist/ui/components/icons/minus.js +1 -0
- package/dist/ui/components/icons/search.js +1 -0
- package/dist/ui/components/image-distortion.js +1 -0
- package/dist/ui/components/input.d.ts +1 -0
- package/dist/ui/components/input.js +21 -0
- package/dist/ui/components/label.d.ts +1 -0
- package/dist/ui/components/label.js +18 -0
- package/dist/ui/components/leva-client.js +1 -0
- package/dist/ui/components/list-item.js +3 -2
- package/dist/ui/components/overlays/blend-modes.js +1 -0
- package/dist/ui/components/overlays/glitch.js +1 -0
- package/dist/ui/components/overlays/greys.js +1 -0
- package/dist/ui/components/overlays/index.js +1 -0
- package/dist/ui/components/overlays/lens-layers.js +1 -0
- package/dist/ui/components/overlays/lens.js +1 -0
- package/dist/ui/components/overlays/noise.js +1 -0
- package/dist/ui/components/overlays/vignette.js +1 -0
- package/dist/ui/components/poster.js +1 -0
- package/dist/ui/components/progress.js +1 -0
- package/dist/ui/components/scene-canvas.js +1 -0
- package/dist/ui/components/scramble.js +1 -0
- package/dist/ui/components/segmented.js +5 -4
- package/dist/ui/components/select.js +1 -0
- package/dist/ui/components/selection-switcher.js +1 -0
- package/dist/ui/components/separator.d.ts +5 -0
- package/dist/ui/components/separator.js +22 -0
- package/dist/ui/components/shader.js +1 -0
- package/dist/ui/components/socials.js +1 -0
- package/dist/ui/components/spinner.js +1 -0
- package/dist/ui/components/stats.js +2 -1
- package/dist/ui/components/switch.js +1 -0
- package/dist/ui/components/tabs.js +4 -3
- package/dist/ui/components/terminal-demo.js +2 -1
- package/dist/ui/components/theme-toggle.js +1 -0
- package/dist/ui/components/tier-card.js +2 -1
- package/dist/ui/components/toast.d.ts +8 -0
- package/dist/ui/components/toast.js +39 -0
- package/dist/ui/components/tv.js +1 -0
- package/dist/ui/components/typography/h1.js +1 -0
- package/dist/ui/components/typography/h2.js +1 -0
- package/dist/ui/components/typography/index.js +1 -0
- package/dist/ui/components/typography/legend.js +1 -0
- package/dist/ui/components/typography/small.js +1 -0
- package/dist/ui/components/watchlist.js +2 -1
- package/dist/ui/footer.js +1 -0
- package/dist/ui/globals.css +47 -3
- package/dist/ui/header.js +1 -0
- package/dist/ui/layout-wrapper.js +2 -1
- package/dist/utils/color.js +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/poly.js +1 -0
- package/package.json +5 -3
- package/src/assets/filler-bg0.webp +0 -0
- package/src/assets.d.ts +38 -0
- package/src/fonts/Collapse-Bold.woff2 +0 -0
- package/src/fonts/Collapse-BoldItalic.woff2 +0 -0
- package/src/fonts/Collapse-Italic.woff2 +0 -0
- package/src/fonts/Collapse-Light.woff2 +0 -0
- package/src/fonts/Collapse-LightItalic.woff2 +0 -0
- package/src/fonts/Collapse-Regular.woff2 +0 -0
- package/src/fonts/Collapse-Thin.woff2 +0 -0
- package/src/fonts/Collapse-ThinItalic.woff2 +0 -0
- package/src/fonts/Mondwest-Regular.woff2 +0 -0
- package/src/fonts/Neuebit-Bold.woff2 +0 -0
- package/src/fonts/RulesCompressed-Medium.woff2 +0 -0
- package/src/fonts/RulesCompressed-Regular.woff2 +0 -0
- package/src/fonts/RulesExpanded-Bold.woff2 +0 -0
- package/src/fonts/RulesExpanded-Regular.woff2 +0 -0
- package/src/fonts.ts +6 -0
- package/src/hooks/use-below-breakpoint.ts +21 -0
- package/src/hooks/use-capped-frame.ts +18 -0
- package/src/hooks/use-confirm-delete.ts +43 -0
- package/src/hooks/use-css-var-dims.ts +39 -0
- package/src/hooks/use-gpu-tier.ts +165 -0
- package/src/hooks/use-render-loop.ts +121 -0
- package/src/hooks/use-smooth-controls.ts +318 -0
- package/src/hooks/use-toast.ts +29 -0
- package/src/index.ts +130 -0
- package/src/ui/basic-page.tsx +34 -0
- package/src/ui/build.css +4 -0
- package/src/ui/components/animated-count.stories.tsx +67 -0
- package/src/ui/components/animated-count.tsx +168 -0
- package/src/ui/components/ascii.stories.tsx +30 -0
- package/src/ui/components/ascii.tsx +110 -0
- package/src/ui/components/badge.stories.tsx +31 -0
- package/src/ui/components/badge.tsx +60 -0
- package/src/ui/components/badges/nous-girl.tsx +52 -0
- package/src/ui/components/blend-mode.stories.tsx +33 -0
- package/src/ui/components/blend-mode.tsx +129 -0
- package/src/ui/components/blink.stories.tsx +32 -0
- package/src/ui/components/blink.tsx +21 -0
- package/src/ui/components/bottom-sheet.stories.tsx +43 -0
- package/src/ui/components/bottom-sheet.tsx +227 -0
- package/src/ui/components/button.stories.tsx +68 -0
- package/src/ui/components/button.tsx +170 -0
- package/src/ui/components/card.stories.tsx +63 -0
- package/src/ui/components/card.tsx +85 -0
- package/src/ui/components/checkbox.stories.tsx +113 -0
- package/src/ui/components/checkbox.tsx +36 -0
- package/src/ui/components/command-block.stories.tsx +52 -0
- package/src/ui/components/command-block.tsx +86 -0
- package/src/ui/components/confirm-dialog.stories.tsx +91 -0
- package/src/ui/components/confirm-dialog.tsx +130 -0
- package/src/ui/components/cursor.tsx +115 -0
- package/src/ui/components/dialog.stories.tsx +169 -0
- package/src/ui/components/dialog.tsx +177 -0
- package/src/ui/components/dropdown-menu.stories.tsx +52 -0
- package/src/ui/components/dropdown-menu.tsx +117 -0
- package/src/ui/components/fit-text/fit-text.css +42 -0
- package/src/ui/components/fit-text/index.stories.tsx +33 -0
- package/src/ui/components/fit-text/index.tsx +45 -0
- package/src/ui/components/forms.stories.tsx +173 -0
- package/src/ui/components/graphs/bar-chart.tsx +153 -0
- package/src/ui/components/graphs/index.stories.tsx +64 -0
- package/src/ui/components/graphs/index.tsx +4 -0
- package/src/ui/components/graphs/line-chart.tsx +213 -0
- package/src/ui/components/graphs/utils.tsx +265 -0
- package/src/ui/components/grid/grid.css +79 -0
- package/src/ui/components/grid/index.tsx +19 -0
- package/src/ui/components/hover-bg.stories.tsx +29 -0
- package/src/ui/components/hover-bg.tsx +15 -0
- package/src/ui/components/icons/arrow.tsx +42 -0
- package/src/ui/components/icons/check.tsx +14 -0
- package/src/ui/components/icons/chevron.tsx +45 -0
- package/src/ui/components/icons/discord.tsx +16 -0
- package/src/ui/components/icons/eye.tsx +12 -0
- package/src/ui/components/icons/gear.tsx +51 -0
- package/src/ui/components/icons/github.tsx +16 -0
- package/src/ui/components/icons/hamburger.tsx +52 -0
- package/src/ui/components/icons/heart.tsx +12 -0
- package/src/ui/components/icons/index.ts +12 -0
- package/src/ui/components/icons/link.tsx +14 -0
- package/src/ui/components/icons/minus.tsx +14 -0
- package/src/ui/components/icons/search.tsx +28 -0
- package/src/ui/components/image-distortion.stories.tsx +120 -0
- package/src/ui/components/image-distortion.tsx +498 -0
- package/src/ui/components/input.stories.tsx +39 -0
- package/src/ui/components/input.tsx +20 -0
- package/src/ui/components/label.stories.tsx +26 -0
- package/src/ui/components/label.tsx +16 -0
- package/src/ui/components/leva-client.tsx +14 -0
- package/src/ui/components/list-item.stories.tsx +83 -0
- package/src/ui/components/list-item.tsx +37 -0
- package/src/ui/components/overlays/blend-modes.ts +13 -0
- package/src/ui/components/overlays/glitch.tsx +243 -0
- package/src/ui/components/overlays/greys.tsx +386 -0
- package/src/ui/components/overlays/index.tsx +47 -0
- package/src/ui/components/overlays/lens-layers.tsx +119 -0
- package/src/ui/components/overlays/lens.ts +91 -0
- package/src/ui/components/overlays/noise.tsx +174 -0
- package/src/ui/components/overlays/vignette.tsx +60 -0
- package/src/ui/components/poster.stories.tsx +513 -0
- package/src/ui/components/poster.tsx +411 -0
- package/src/ui/components/progress.stories.tsx +48 -0
- package/src/ui/components/progress.tsx +56 -0
- package/src/ui/components/scene-canvas.tsx +254 -0
- package/src/ui/components/scramble.stories.tsx +49 -0
- package/src/ui/components/scramble.tsx +95 -0
- package/src/ui/components/segmented.stories.tsx +101 -0
- package/src/ui/components/segmented.tsx +81 -0
- package/src/ui/components/select.stories.tsx +88 -0
- package/src/ui/components/select.tsx +267 -0
- package/src/ui/components/selection-switcher.tsx +44 -0
- package/src/ui/components/separator.stories.tsx +33 -0
- package/src/ui/components/separator.tsx +24 -0
- package/src/ui/components/shader.tsx +83 -0
- package/src/ui/components/socials.tsx +42 -0
- package/src/ui/components/spinner.stories.tsx +101 -0
- package/src/ui/components/spinner.tsx +60 -0
- package/src/ui/components/stats.stories.tsx +24 -0
- package/src/ui/components/stats.tsx +53 -0
- package/src/ui/components/switch.stories.tsx +77 -0
- package/src/ui/components/switch.tsx +48 -0
- package/src/ui/components/tabs.stories.tsx +101 -0
- package/src/ui/components/tabs.tsx +66 -0
- package/src/ui/components/terminal-demo.stories.tsx +67 -0
- package/src/ui/components/terminal-demo.tsx +189 -0
- package/src/ui/components/theme-toggle.stories.tsx +47 -0
- package/src/ui/components/theme-toggle.tsx +66 -0
- package/src/ui/components/tier-card.stories.tsx +217 -0
- package/src/ui/components/tier-card.tsx +190 -0
- package/src/ui/components/toast.stories.tsx +55 -0
- package/src/ui/components/toast.tsx +49 -0
- package/src/ui/components/tv.stories.tsx +37 -0
- package/src/ui/components/tv.tsx +257 -0
- package/src/ui/components/typography/h1.tsx +18 -0
- package/src/ui/components/typography/h2.tsx +18 -0
- package/src/ui/components/typography/index.tsx +54 -0
- package/src/ui/components/typography/legend.tsx +24 -0
- package/src/ui/components/typography/small.tsx +11 -0
- package/src/ui/components/watchlist.stories.tsx +33 -0
- package/src/ui/components/watchlist.tsx +105 -0
- package/src/ui/fonts.css +63 -0
- package/src/ui/footer.tsx +111 -0
- package/src/ui/globals.css +395 -0
- package/src/ui/header.tsx +398 -0
- package/src/ui/layout-wrapper.tsx +11 -0
- package/src/utils/color.ts +21 -0
- package/src/utils/index.ts +62 -0
- package/src/utils/poly.ts +26 -0
- package/dist/ui/components/modal/index.d.ts +0 -8
- package/dist/ui/components/modal/index.js +0 -34
- package/dist/ui/components/modal/modal.css +0 -36
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type PointerEvent as ReactPointerEvent,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
useEffect,
|
|
7
|
+
useRef,
|
|
8
|
+
useState
|
|
9
|
+
} from 'react'
|
|
10
|
+
import { createPortal } from 'react-dom'
|
|
11
|
+
|
|
12
|
+
import { cn } from '../../utils'
|
|
13
|
+
import { Typography } from './typography'
|
|
14
|
+
|
|
15
|
+
const CLOSE_DRAG_MIN_PX = 72
|
|
16
|
+
const CLOSE_DRAG_RATIO = 0.18
|
|
17
|
+
const SHEET_TRANSITION_MS = 280
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Mobile-first bottom sheet with slide + fade enter/exit, drag-to-dismiss
|
|
21
|
+
* handle, body scroll lock, and reduced-motion support. Portaled to
|
|
22
|
+
* `document.body` to escape ancestor stacking contexts.
|
|
23
|
+
*/
|
|
24
|
+
export function BottomSheet({
|
|
25
|
+
backdropDismissLabel = 'Dismiss',
|
|
26
|
+
children,
|
|
27
|
+
onClose,
|
|
28
|
+
open,
|
|
29
|
+
title
|
|
30
|
+
}: BottomSheetProps) {
|
|
31
|
+
const [renderPortal, setRenderPortal] = useState(open)
|
|
32
|
+
const [entered, setEntered] = useState(false)
|
|
33
|
+
const [dragOffsetPx, setDragOffsetPx] = useState(0)
|
|
34
|
+
const [dragActive, setDragActive] = useState(false)
|
|
35
|
+
|
|
36
|
+
const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
37
|
+
const sheetRef = useRef<HTMLDivElement>(null)
|
|
38
|
+
const dragTrackingRef = useRef(false)
|
|
39
|
+
const dragStartYRef = useRef(0)
|
|
40
|
+
const dragOffsetRef = useRef(0)
|
|
41
|
+
|
|
42
|
+
const reducedMotion =
|
|
43
|
+
typeof window !== 'undefined' &&
|
|
44
|
+
window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
45
|
+
|
|
46
|
+
const syncDragPx = (next: number) => {
|
|
47
|
+
dragOffsetRef.current = next
|
|
48
|
+
setDragOffsetPx(next)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (closeTimerRef.current) {
|
|
53
|
+
clearTimeout(closeTimerRef.current)
|
|
54
|
+
closeTimerRef.current = null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const ms = reducedMotion ? 0 : SHEET_TRANSITION_MS
|
|
58
|
+
|
|
59
|
+
let openRafId = 0
|
|
60
|
+
let exitRafId = 0
|
|
61
|
+
|
|
62
|
+
if (open) {
|
|
63
|
+
openRafId = requestAnimationFrame(() => {
|
|
64
|
+
dragTrackingRef.current = false
|
|
65
|
+
dragOffsetRef.current = 0
|
|
66
|
+
setDragActive(false)
|
|
67
|
+
setDragOffsetPx(0)
|
|
68
|
+
setRenderPortal(true)
|
|
69
|
+
requestAnimationFrame(() => {
|
|
70
|
+
requestAnimationFrame(() => setEntered(true))
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
} else {
|
|
74
|
+
exitRafId = requestAnimationFrame(() => {
|
|
75
|
+
dragTrackingRef.current = false
|
|
76
|
+
setDragActive(false)
|
|
77
|
+
setEntered(false)
|
|
78
|
+
closeTimerRef.current = window.setTimeout(() => {
|
|
79
|
+
dragOffsetRef.current = 0
|
|
80
|
+
setDragOffsetPx(0)
|
|
81
|
+
setRenderPortal(false)
|
|
82
|
+
closeTimerRef.current = null
|
|
83
|
+
}, ms)
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return () => {
|
|
88
|
+
cancelAnimationFrame(openRafId)
|
|
89
|
+
cancelAnimationFrame(exitRafId)
|
|
90
|
+
if (closeTimerRef.current) {
|
|
91
|
+
clearTimeout(closeTimerRef.current)
|
|
92
|
+
closeTimerRef.current = null
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}, [open, reducedMotion])
|
|
96
|
+
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (!renderPortal) return
|
|
99
|
+
const prev = document.body.style.overflow
|
|
100
|
+
document.body.style.overflow = 'hidden'
|
|
101
|
+
return () => {
|
|
102
|
+
document.body.style.overflow = prev
|
|
103
|
+
}
|
|
104
|
+
}, [renderPortal])
|
|
105
|
+
|
|
106
|
+
if (!renderPortal || typeof document === 'undefined') return null
|
|
107
|
+
|
|
108
|
+
const durationClass = reducedMotion ? 'duration-0' : 'duration-[280ms]'
|
|
109
|
+
const draggingVisual = dragActive || dragOffsetPx > 0
|
|
110
|
+
|
|
111
|
+
const onDragPointerDown = (e: ReactPointerEvent<HTMLDivElement>) => {
|
|
112
|
+
if (reducedMotion || !entered) return
|
|
113
|
+
if (e.pointerType === 'mouse' && e.button !== 0) return
|
|
114
|
+
|
|
115
|
+
dragTrackingRef.current = true
|
|
116
|
+
setDragActive(true)
|
|
117
|
+
dragStartYRef.current = e.clientY
|
|
118
|
+
syncDragPx(0)
|
|
119
|
+
e.currentTarget.setPointerCapture(e.pointerId)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const onDragPointerMove = (e: ReactPointerEvent<HTMLDivElement>) => {
|
|
123
|
+
if (!dragTrackingRef.current) return
|
|
124
|
+
const dy = e.clientY - dragStartYRef.current
|
|
125
|
+
const next = Math.max(0, dy)
|
|
126
|
+
const sheetH = sheetRef.current?.offsetHeight ?? 560
|
|
127
|
+
syncDragPx(Math.min(next, sheetH))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const endDrag = (e: ReactPointerEvent<HTMLDivElement>) => {
|
|
131
|
+
if (!dragTrackingRef.current) return
|
|
132
|
+
dragTrackingRef.current = false
|
|
133
|
+
setDragActive(false)
|
|
134
|
+
try {
|
|
135
|
+
e.currentTarget.releasePointerCapture(e.pointerId)
|
|
136
|
+
} catch {
|
|
137
|
+
/* already released */
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const sheetH = sheetRef.current?.offsetHeight ?? 560
|
|
141
|
+
const threshold = Math.max(CLOSE_DRAG_MIN_PX, sheetH * CLOSE_DRAG_RATIO)
|
|
142
|
+
const d = dragOffsetRef.current
|
|
143
|
+
|
|
144
|
+
if (d >= threshold) {
|
|
145
|
+
onClose()
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
syncDragPx(0)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return createPortal(
|
|
152
|
+
<div className="fixed inset-0 z-[200] flex flex-col justify-end">
|
|
153
|
+
<button
|
|
154
|
+
aria-label={backdropDismissLabel}
|
|
155
|
+
className={cn(
|
|
156
|
+
'absolute inset-0 bg-black/55 backdrop-blur-[2px]',
|
|
157
|
+
'transition-opacity ease-out motion-reduce:transition-none',
|
|
158
|
+
durationClass,
|
|
159
|
+
entered ? 'opacity-100' : 'opacity-0'
|
|
160
|
+
)}
|
|
161
|
+
onClick={onClose}
|
|
162
|
+
type="button"
|
|
163
|
+
/>
|
|
164
|
+
|
|
165
|
+
<div
|
|
166
|
+
aria-label={title}
|
|
167
|
+
aria-modal="true"
|
|
168
|
+
className={cn(
|
|
169
|
+
'relative flex max-h-[85dvh] min-h-0 flex-col rounded-t-xl border border-current/20',
|
|
170
|
+
'bg-background-base/98 pb-[max(1rem,env(safe-area-inset-bottom))]',
|
|
171
|
+
'shadow-[0_-12px_40px_-8px_rgba(0,0,0,0.55)] backdrop-blur-md',
|
|
172
|
+
'ease-out motion-reduce:transition-none transform-gpu',
|
|
173
|
+
draggingVisual
|
|
174
|
+
? 'transition-none'
|
|
175
|
+
: cn('transition-transform', durationClass),
|
|
176
|
+
entered ? 'translate-y-0' : 'translate-y-full'
|
|
177
|
+
)}
|
|
178
|
+
ref={sheetRef}
|
|
179
|
+
role="dialog"
|
|
180
|
+
style={
|
|
181
|
+
entered && dragOffsetPx > 0
|
|
182
|
+
? { transform: `translateY(${dragOffsetPx}px)` }
|
|
183
|
+
: undefined
|
|
184
|
+
}
|
|
185
|
+
>
|
|
186
|
+
<div
|
|
187
|
+
className={cn(
|
|
188
|
+
'flex shrink-0 flex-col gap-2 border-b border-current/15 px-4 pb-3 pt-2',
|
|
189
|
+
'touch-none select-none',
|
|
190
|
+
reducedMotion
|
|
191
|
+
? 'cursor-default'
|
|
192
|
+
: 'cursor-grab active:cursor-grabbing'
|
|
193
|
+
)}
|
|
194
|
+
onPointerCancel={endDrag}
|
|
195
|
+
onPointerDown={onDragPointerDown}
|
|
196
|
+
onPointerMove={onDragPointerMove}
|
|
197
|
+
onPointerUp={endDrag}
|
|
198
|
+
>
|
|
199
|
+
<div
|
|
200
|
+
aria-hidden
|
|
201
|
+
className="mx-auto h-1 w-10 shrink-0 rounded-full bg-current/20"
|
|
202
|
+
/>
|
|
203
|
+
|
|
204
|
+
<Typography
|
|
205
|
+
className="text-[0.65rem] tracking-[0.15em] uppercase text-midground/70"
|
|
206
|
+
mondwest
|
|
207
|
+
>
|
|
208
|
+
{title}
|
|
209
|
+
</Typography>
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
<div className="min-h-0 flex-1 overflow-y-auto overscroll-contain">
|
|
213
|
+
{children}
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
</div>,
|
|
217
|
+
document.body
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
interface BottomSheetProps {
|
|
222
|
+
backdropDismissLabel?: string
|
|
223
|
+
children: ReactNode
|
|
224
|
+
onClose: () => void
|
|
225
|
+
open: boolean
|
|
226
|
+
title: string
|
|
227
|
+
}
|
|
@@ -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/Forms/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,63 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
|
|
3
|
+
import { Button } from './button'
|
|
4
|
+
import {
|
|
5
|
+
Card,
|
|
6
|
+
CardContent,
|
|
7
|
+
CardDescription,
|
|
8
|
+
CardHeader,
|
|
9
|
+
CardTitle
|
|
10
|
+
} from './card'
|
|
11
|
+
import { Input } from './input'
|
|
12
|
+
import { Label } from './label'
|
|
13
|
+
import { Separator } from './separator'
|
|
14
|
+
|
|
15
|
+
const meta: Meta<typeof Card> = {
|
|
16
|
+
component: Card,
|
|
17
|
+
title: 'Components/Data Display/Card'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default meta
|
|
21
|
+
|
|
22
|
+
type Story = StoryObj<typeof Card>
|
|
23
|
+
|
|
24
|
+
export const Default: Story = {
|
|
25
|
+
render: () => (
|
|
26
|
+
<Card className="max-w-sm">
|
|
27
|
+
<CardHeader>
|
|
28
|
+
<CardTitle>Card title</CardTitle>
|
|
29
|
+
<CardDescription>A brief description of this card.</CardDescription>
|
|
30
|
+
</CardHeader>
|
|
31
|
+
<CardContent>
|
|
32
|
+
<p className="text-sm text-midground/70">
|
|
33
|
+
Card body content goes here.
|
|
34
|
+
</p>
|
|
35
|
+
</CardContent>
|
|
36
|
+
</Card>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const WithForm: Story = {
|
|
41
|
+
render: () => (
|
|
42
|
+
<Card className="max-w-sm">
|
|
43
|
+
<CardHeader>
|
|
44
|
+
<CardTitle>Settings</CardTitle>
|
|
45
|
+
<CardDescription>Configure your preferences.</CardDescription>
|
|
46
|
+
</CardHeader>
|
|
47
|
+
<CardContent>
|
|
48
|
+
<div className="grid gap-3">
|
|
49
|
+
<div className="grid gap-1.5">
|
|
50
|
+
<Label htmlFor="card-name">Name</Label>
|
|
51
|
+
<Input id="card-name" placeholder="Enter name…" />
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<Separator />
|
|
55
|
+
|
|
56
|
+
<div className="flex justify-end">
|
|
57
|
+
<Button>Save</Button>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</CardContent>
|
|
61
|
+
</Card>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { cn } from '../../utils'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Themeable card primitive. Themes can restyle every card by setting CSS
|
|
5
|
+
* custom properties:
|
|
6
|
+
*
|
|
7
|
+
* --component-card-clip-path
|
|
8
|
+
* --component-card-border-image
|
|
9
|
+
* --component-card-background
|
|
10
|
+
* --component-card-box-shadow
|
|
11
|
+
*
|
|
12
|
+
* All are optional — unset vars compute to their CSS initial value.
|
|
13
|
+
*/
|
|
14
|
+
const CARD_STYLE: React.CSSProperties = {
|
|
15
|
+
background: 'var(--component-card-background)',
|
|
16
|
+
borderImage: 'var(--component-card-border-image)',
|
|
17
|
+
boxShadow: 'var(--component-card-box-shadow)',
|
|
18
|
+
clipPath: 'var(--component-card-clip-path)'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function Card({
|
|
22
|
+
className,
|
|
23
|
+
style,
|
|
24
|
+
...props
|
|
25
|
+
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
className={cn(
|
|
29
|
+
'border border-midground/15 bg-background-base/80 text-midground w-full',
|
|
30
|
+
className
|
|
31
|
+
)}
|
|
32
|
+
style={{ ...CARD_STYLE, ...style }}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function CardHeader({
|
|
39
|
+
className,
|
|
40
|
+
...props
|
|
41
|
+
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
className={cn(
|
|
45
|
+
'flex flex-col gap-1.5 p-4 border-b border-midground/15',
|
|
46
|
+
className
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function CardTitle({
|
|
54
|
+
className,
|
|
55
|
+
...props
|
|
56
|
+
}: React.HTMLAttributes<HTMLHeadingElement>) {
|
|
57
|
+
return (
|
|
58
|
+
<h3
|
|
59
|
+
className={cn(
|
|
60
|
+
'font-expanded text-sm font-bold tracking-[0.08em] uppercase',
|
|
61
|
+
className
|
|
62
|
+
)}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function CardDescription({
|
|
69
|
+
className,
|
|
70
|
+
...props
|
|
71
|
+
}: React.HTMLAttributes<HTMLParagraphElement>) {
|
|
72
|
+
return (
|
|
73
|
+
<p
|
|
74
|
+
className={cn('font-mondwest text-xs text-midground/60', className)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function CardContent({
|
|
81
|
+
className,
|
|
82
|
+
...props
|
|
83
|
+
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
84
|
+
return <div className={cn('p-4', className)} {...props} />
|
|
85
|
+
}
|