@passui/react 0.1.35 → 0.1.37

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 (33) hide show
  1. package/dist/types/src/components/accordion/accordion.d.ts +5 -1
  2. package/dist/types/src/components/date-picker/date-picker.d.ts +4 -1
  3. package/dist/types/src/components/dialog/dialog.d.ts +4 -1
  4. package/dist/types/src/components/disclosure-group/disclosure-group.d.ts +4 -1
  5. package/dist/types/src/components/morph/index.d.ts +2 -0
  6. package/dist/types/src/components/morph/morph.d.ts +29 -0
  7. package/dist/types/src/components/morph/use-morph-disclosure.d.ts +13 -0
  8. package/dist/types/src/components/radio-group/radio-group.d.ts +4 -1
  9. package/dist/types/src/components/segmented-control/segmented-control.d.ts +4 -1
  10. package/dist/types/src/components/sheet/sheet.d.ts +3 -0
  11. package/dist/types/src/components/slider/slider.d.ts +5 -2
  12. package/dist/types/src/components/stepper/stepper.d.ts +4 -1
  13. package/dist/types/src/components/swatch-picker/swatch-picker.d.ts +4 -1
  14. package/dist/types/src/components/tab-view/tab-view.d.ts +4 -1
  15. package/dist/types/src/components/toggle/toggle.d.ts +4 -1
  16. package/dist/types/src/index.d.ts +1 -0
  17. package/package.json +5 -1
  18. package/src/components/accordion/accordion.tsx +16 -3
  19. package/src/components/date-picker/date-picker.tsx +37 -6
  20. package/src/components/dialog/dialog.tsx +48 -5
  21. package/src/components/disclosure-group/disclosure-group.tsx +31 -9
  22. package/src/components/morph/index.ts +2 -0
  23. package/src/components/morph/morph.tsx +210 -0
  24. package/src/components/morph/use-morph-disclosure.ts +27 -0
  25. package/src/components/radio-group/radio-group.tsx +30 -10
  26. package/src/components/segmented-control/segmented-control.tsx +47 -6
  27. package/src/components/sheet/sheet.tsx +20 -2
  28. package/src/components/slider/slider.tsx +58 -9
  29. package/src/components/stepper/stepper.tsx +37 -11
  30. package/src/components/swatch-picker/swatch-picker.tsx +40 -5
  31. package/src/components/tab-view/tab-view.tsx +31 -4
  32. package/src/components/toggle/toggle.tsx +105 -20
  33. package/src/index.ts +2 -0
@@ -1,9 +1,36 @@
1
1
  import { useId } from "react"
2
- import { Button } from "../button/button"
2
+ import { Button, type ButtonSize } from "../button/button"
3
3
  import { cn } from "../../lib/class-names"
4
4
  import { clampRange, stepRange } from "../../lib/range-value"
5
5
  import { RollingDigit } from "./rolling-digit"
6
6
 
7
+ export type StepperSize = "sm" | "md" | "lg"
8
+
9
+ const labelText: Record<StepperSize, string> = {
10
+ sm: "min-w-0 text-footnote text-label",
11
+ md: "min-w-0 text-subheadline text-label",
12
+ lg: "min-w-0 text-body text-label",
13
+ }
14
+ const outputText: Record<StepperSize, string> = {
15
+ sm: "text-footnote tabular-nums text-label",
16
+ md: "text-subheadline tabular-nums text-label",
17
+ lg: "text-body tabular-nums text-label",
18
+ }
19
+ const buttonSizes: Record<StepperSize, { size: ButtonSize; cls: string }> = {
20
+ sm: {
21
+ size: "icon-sm",
22
+ cls: "size-7 rounded-none text-subheadline text-accent-text [@media(pointer:coarse)]:size-10",
23
+ },
24
+ md: {
25
+ size: "icon",
26
+ cls: "size-8 rounded-none text-title3 text-accent-text [@media(pointer:coarse)]:size-11",
27
+ },
28
+ lg: {
29
+ size: "icon-lg",
30
+ cls: "size-9 rounded-none text-title2 text-accent-text [@media(pointer:coarse)]:size-12",
31
+ },
32
+ }
33
+
7
34
  export interface StepperProps {
8
35
  value: number
9
36
  onChange: (value: number) => void
@@ -17,6 +44,8 @@ export interface StepperProps {
17
44
  className?: string
18
45
  /** Formats the displayed value. */
19
46
  formatValue?: (value: number) => string
47
+ /** Label, output and +/- button scale. @default "md" */
48
+ size?: StepperSize
20
49
  }
21
50
 
22
51
  /** Discrete increments with disabled boundary actions and a spoken value. */
@@ -31,6 +60,7 @@ export function Stepper({
31
60
  disabled = false,
32
61
  className,
33
62
  formatValue,
63
+ size = "md",
34
64
  }: StepperProps) {
35
65
  const id = useId()
36
66
  const upper = Math.max(min, max)
@@ -43,15 +73,11 @@ export function Stepper({
43
73
 
44
74
  return (
45
75
  <div className={cn("flex min-w-0 items-center justify-between gap-3", className)}>
46
- <span id={id} className="min-w-0 text-subheadline text-label">
76
+ <span id={id} className={labelText[size]}>
47
77
  {label}
48
78
  </span>
49
79
  <div className="flex shrink-0 items-center gap-3">
50
- <output
51
- aria-live="polite"
52
- aria-labelledby={id}
53
- className="text-subheadline tabular-nums text-label"
54
- >
80
+ <output aria-live="polite" aria-labelledby={id} className={outputText[size]}>
55
81
  <span aria-hidden="true" className="inline-flex items-center">
56
82
  {Array.from(displayed).map((character, index) => {
57
83
  if (!/[0-9]/.test(character))
@@ -73,24 +99,24 @@ export function Stepper({
73
99
  <Button
74
100
  type="button"
75
101
  variant="ghost"
76
- size="icon"
102
+ size={buttonSizes[size].size}
77
103
  aria-label={labels.decrement}
78
104
  aria-describedby={id}
79
105
  disabled={disabled || decrement === current}
80
106
  onClick={() => onChange(decrement)}
81
- className="size-8 rounded-none text-title3 text-accent-text [@media(pointer:coarse)]:size-11"
107
+ className={buttonSizes[size].cls}
82
108
  >
83
109
  −
84
110
  </Button>
85
111
  <Button
86
112
  type="button"
87
113
  variant="ghost"
88
- size="icon"
114
+ size={buttonSizes[size].size}
89
115
  aria-label={labels.increment}
90
116
  aria-describedby={id}
91
117
  disabled={disabled || increment === current}
92
118
  onClick={() => onChange(increment)}
93
- className="size-8 rounded-none text-title3 text-accent-text [@media(pointer:coarse)]:size-11"
119
+ className={buttonSizes[size].cls}
94
120
  >
95
121
  +
96
122
  </Button>
@@ -14,6 +14,34 @@ export interface SwatchPickerOption<T extends string> {
14
14
  ink: string
15
15
  }
16
16
 
17
+ export type SwatchPickerSize = "sm" | "md" | "lg"
18
+
19
+ const swatchSizes: Record<SwatchPickerSize, string> = {
20
+ sm: "size-6",
21
+ md: "size-8",
22
+ lg: "size-10",
23
+ }
24
+ const checkSizes: Record<SwatchPickerSize, number> = {
25
+ sm: 13,
26
+ md: 16,
27
+ lg: 19,
28
+ }
29
+ const captionText: Record<SwatchPickerSize, string> = {
30
+ sm: "text-caption2",
31
+ md: "text-caption2",
32
+ lg: "text-caption",
33
+ }
34
+ const fieldsetGaps: Record<SwatchPickerSize, string> = {
35
+ sm: "gap-2",
36
+ md: "gap-3",
37
+ lg: "gap-4",
38
+ }
39
+ const labelGaps: Record<SwatchPickerSize, string> = {
40
+ sm: "gap-1",
41
+ md: "gap-1.5",
42
+ lg: "gap-2",
43
+ }
44
+
17
45
  export interface SwatchPickerProps<T extends string> {
18
46
  value: T
19
47
  options: ReadonlyArray<SwatchPickerOption<T>>
@@ -22,6 +50,8 @@ export interface SwatchPickerProps<T extends string> {
22
50
  label: string
23
51
  /** Custom classes for the swatch container. */
24
52
  className?: string
53
+ /** Swatch diameter, check icon and caption scale. @default "md" */
54
+ size?: SwatchPickerSize
25
55
  }
26
56
 
27
57
  /** Colour swatches with a check on the chosen one. */
@@ -31,17 +61,21 @@ export function SwatchPicker<T extends string>({
31
61
  onChange,
32
62
  label,
33
63
  className,
64
+ size = "md",
34
65
  }: SwatchPickerProps<T>) {
35
66
  const name = useId()
36
67
  return (
37
- <fieldset className={cn("flex flex-wrap gap-3", className)}>
68
+ <fieldset className={cn("flex flex-wrap", fieldsetGaps[size], className)}>
38
69
  <legend className="sr-only">{label}</legend>
39
70
  {options.map((option) => {
40
71
  const selected = option.value === value
41
72
  return (
42
73
  <label
43
74
  key={option.value}
44
- className="group flex cursor-pointer flex-col items-center gap-1.5"
75
+ className={cn(
76
+ "group flex cursor-pointer flex-col items-center",
77
+ labelGaps[size]
78
+ )}
45
79
  >
46
80
  <input
47
81
  type="radio"
@@ -56,7 +90,8 @@ export function SwatchPicker<T extends string>({
56
90
  />
57
91
  <span
58
92
  className={cn(
59
- "flex size-8 items-center justify-center rounded-full ring-offset-1 ring-offset-elevated transition-[box-shadow,transform] duration-fast group-active:scale-95 peer-focus-visible:ring-2 peer-focus-visible:ring-accent",
93
+ "flex items-center justify-center rounded-full ring-offset-1 ring-offset-elevated transition-[box-shadow,transform] duration-fast group-active:scale-95 peer-focus-visible:ring-2 peer-focus-visible:ring-accent",
94
+ swatchSizes[size],
60
95
  selected
61
96
  ? "ring-[1.5px] ring-label/70"
62
97
  : "ring-0 group-hover:ring-[1.5px] group-hover:ring-separator"
@@ -78,7 +113,7 @@ export function SwatchPicker<T extends string>({
78
113
  transition={spring.bouncy}
79
114
  >
80
115
  <CheckIcon
81
- size={16}
116
+ size={checkSizes[size]}
82
117
  strokeWidth={2.6}
83
118
  style={{
84
119
  color: option.ink,
@@ -89,7 +124,7 @@ export function SwatchPicker<T extends string>({
89
124
  </span>
90
125
  <span
91
126
  className={cn(
92
- "text-caption2",
127
+ captionText[size],
93
128
  selected ? "font-semibold text-label" : "text-label-tertiary"
94
129
  )}
95
130
  >
@@ -2,6 +2,28 @@ import { useId, useRef, useState, type ReactNode } from "react"
2
2
  import { cn } from "../../lib/class-names"
3
3
 
4
4
  export type TabViewAlign = "start" | "center"
5
+ export type TabViewSize = "sm" | "md" | "lg"
6
+
7
+ const containerGaps: Record<TabViewSize, string> = {
8
+ sm: "gap-4",
9
+ md: "gap-5",
10
+ lg: "gap-6",
11
+ }
12
+ const tablistClasses: Record<TabViewSize, string> = {
13
+ sm: "inline-flex h-7 w-fit max-w-full items-center gap-4 p-[2px]",
14
+ md: "inline-flex h-8 w-fit max-w-full items-center gap-5 p-[3px]",
15
+ lg: "inline-flex h-10 w-fit max-w-full items-center gap-6 p-[4px]",
16
+ }
17
+ const tabText: Record<TabViewSize, string> = {
18
+ sm: "text-footnote",
19
+ md: "text-subheadline",
20
+ lg: "text-body",
21
+ }
22
+ const underlineOffset: Record<TabViewSize, string> = {
23
+ sm: "after:bottom-[-4px] after:h-0.5",
24
+ md: "after:bottom-[-5px] after:h-0.5",
25
+ lg: "after:bottom-[-6px] after:h-1",
26
+ }
5
27
 
6
28
  export interface TabViewProps<Value extends string> {
7
29
  /** Available tabs, in keyboard navigation order. */
@@ -17,6 +39,8 @@ export interface TabViewProps<Value extends string> {
17
39
  label: string
18
40
  /** Renders the selected tab's panel. */
19
41
  children: (value: Value) => ReactNode
42
+ /** Tablist height, tab text scale and underline offset. @default "md" */
43
+ size?: TabViewSize
20
44
  className?: string
21
45
  }
22
46
 
@@ -29,6 +53,7 @@ export function TabView<Value extends string>({
29
53
  align = "center",
30
54
  label,
31
55
  children,
56
+ size = "md",
32
57
  className,
33
58
  }: TabViewProps<Value>) {
34
59
  const id = useId()
@@ -44,12 +69,12 @@ export function TabView<Value extends string>({
44
69
  if (value === undefined) return null
45
70
 
46
71
  return (
47
- <div className={cn("flex w-full flex-col gap-5", className)}>
72
+ <div className={cn("flex w-full flex-col", containerGaps[size], className)}>
48
73
  <div
49
74
  role="tablist"
50
75
  aria-label={label}
51
76
  className={cn(
52
- "inline-flex h-8 w-fit max-w-full items-center gap-5 p-[3px]",
77
+ tablistClasses[size],
53
78
  align === "start" ? "self-start" : "mx-auto justify-center"
54
79
  )}
55
80
  >
@@ -96,9 +121,11 @@ export function TabView<Value extends string>({
96
121
  }
97
122
  }}
98
123
  className={cn(
99
- "relative inline-flex h-[calc(100%-1px)] shrink-0 items-center justify-center border border-transparent p-0 text-subheadline font-medium whitespace-nowrap",
124
+ "relative inline-flex h-[calc(100%-1px)] shrink-0 items-center justify-center border border-transparent p-0 font-medium whitespace-nowrap",
125
+ tabText[size],
100
126
  "focus-visible:outline-2 focus-visible:outline-offset-2",
101
- "after:absolute after:inset-x-0 after:bottom-[-5px] after:h-0.5 after:bg-current",
127
+ "after:absolute after:inset-x-0 after:bg-current",
128
+ underlineOffset[size],
102
129
  option.value === value
103
130
  ? "after:opacity-100"
104
131
  : "opacity-60 hover:opacity-100 after:opacity-0"
@@ -13,6 +13,74 @@ import { haptic } from "../../lib/haptics"
13
13
  import { spring } from "../../motion/transitions"
14
14
 
15
15
  export type ToggleStyle = "switch" | "checkbox"
16
+ export type ToggleSize = "sm" | "md" | "lg"
17
+
18
+ const labelText: Record<ToggleSize, string> = {
19
+ sm: "text-footnote",
20
+ md: "text-subheadline",
21
+ lg: "text-body",
22
+ }
23
+ const labelGap: Record<ToggleSize, string> = {
24
+ sm: "gap-2",
25
+ md: "gap-3",
26
+ lg: "gap-3.5",
27
+ }
28
+ const checkboxBox: Record<ToggleSize, string> = {
29
+ sm: "size-4 rounded-sm",
30
+ md: "size-5 rounded-sm",
31
+ lg: "size-6 rounded-md",
32
+ }
33
+ const checkIconSize: Record<ToggleSize, number> = {
34
+ sm: 12,
35
+ md: 14,
36
+ lg: 16,
37
+ }
38
+ const mixedBar: Record<ToggleSize, string> = {
39
+ sm: "h-0.5 w-2",
40
+ md: "h-0.5 w-2.5",
41
+ lg: "h-0.5 w-3",
42
+ }
43
+
44
+ // Switch physics: travel = track_width - thumb_width - 2*inset.
45
+ // md: 50 - 26 - 2 = 22 ✓ sm: 44 - 22 - 2 = 20 ✓ lg: 56 - 30 - 2 = 24 ✓
46
+ type SwitchGeo = {
47
+ travel: number
48
+ inset: number
49
+ dropOffset: number
50
+ track: string
51
+ thumb: string
52
+ drop: string
53
+ dropTop: string
54
+ }
55
+ const switchGeo: Record<ToggleSize, SwitchGeo> = {
56
+ sm: {
57
+ travel: 20,
58
+ inset: 2,
59
+ dropOffset: 4,
60
+ track: "h-[26px] w-[44px]",
61
+ thumb: "size-[22px]",
62
+ drop: "size-3.5",
63
+ dropTop: "top-[6px]",
64
+ },
65
+ md: {
66
+ travel: 22,
67
+ inset: 2,
68
+ dropOffset: 5,
69
+ track: "h-[30px] w-[50px]",
70
+ thumb: "size-[26px]",
71
+ drop: "size-4",
72
+ dropTop: "top-[7px]",
73
+ },
74
+ lg: {
75
+ travel: 24,
76
+ inset: 2,
77
+ dropOffset: 6,
78
+ track: "h-[34px] w-[56px]",
79
+ thumb: "size-[30px]",
80
+ drop: "size-5",
81
+ dropTop: "top-[8px]",
82
+ },
83
+ }
16
84
 
17
85
  export interface ToggleProps {
18
86
  /** Current on or off state. */
@@ -28,6 +96,8 @@ export interface ToggleProps {
28
96
  mixed?: boolean
29
97
  /** Keeps the accessible name while hiding the visible label. @default false */
30
98
  labelHidden?: boolean
99
+ /** Switch track size, thumb travel, checkbox box and label text scale. @default "md" */
100
+ size?: ToggleSize
31
101
  /** Custom classes for the toggle container. */
32
102
  className?: string
33
103
  }
@@ -40,6 +110,7 @@ export function Toggle({
40
110
  style = "switch",
41
111
  mixed = false,
42
112
  labelHidden = false,
113
+ size = "md",
43
114
  className,
44
115
  }: ToggleProps) {
45
116
  const input = useRef<HTMLInputElement>(null)
@@ -50,12 +121,14 @@ export function Toggle({
50
121
  return (
51
122
  <label
52
123
  className={cn(
53
- "flex cursor-pointer items-center gap-3 text-subheadline",
124
+ "flex cursor-pointer items-center",
125
+ labelGap[size],
126
+ labelText[size],
54
127
  disabled && "cursor-not-allowed opacity-40",
55
128
  className
56
129
  )}
57
130
  >
58
- <span className="relative inline-flex size-5 shrink-0">
131
+ <span className={cn("relative inline-flex shrink-0", checkboxBox[size])}>
59
132
  <input
60
133
  ref={input}
61
134
  type="checkbox"
@@ -64,19 +137,23 @@ export function Toggle({
64
137
  disabled={disabled}
65
138
  onChange={(event) => onChange(event.target.checked)}
66
139
  className={cn(
67
- "peer size-5 cursor-inherit appearance-none rounded-sm border border-separator/80 bg-elevated transition-colors checked:border-transparent checked:bg-accent indeterminate:border-transparent indeterminate:bg-accent motion-reduce:transition-none",
140
+ "peer cursor-inherit appearance-none border border-separator/80 bg-elevated transition-colors checked:border-transparent checked:bg-accent indeterminate:border-transparent indeterminate:bg-accent motion-reduce:transition-none",
141
+ checkboxBox[size],
68
142
  "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-text"
69
143
  )}
70
144
  />
71
145
  {mixed ? (
72
146
  <span
73
147
  aria-hidden="true"
74
- className="pointer-events-none absolute inset-0 m-auto h-0.5 w-2.5 rounded-full bg-on-accent"
148
+ className={cn(
149
+ "pointer-events-none absolute inset-0 m-auto rounded-full bg-on-accent",
150
+ mixedBar[size]
151
+ )}
75
152
  />
76
153
  ) : (
77
154
  <CheckIcon
78
155
  aria-hidden="true"
79
- size={14}
156
+ size={checkIconSize[size]}
80
157
  strokeWidth={2.5}
81
158
  className="pointer-events-none absolute inset-0 m-auto text-on-accent opacity-0 peer-checked:opacity-100"
82
159
  />
@@ -92,6 +169,7 @@ export function Toggle({
92
169
  onChange={onChange}
93
170
  label={label}
94
171
  disabled={disabled}
172
+ size={size}
95
173
  className={className}
96
174
  />
97
175
  )
@@ -102,30 +180,32 @@ function SwitchToggle({
102
180
  onChange,
103
181
  label,
104
182
  disabled,
183
+ size = "md",
105
184
  className,
106
- }: Pick<ToggleProps, "checked" | "onChange" | "label" | "disabled" | "className">) {
185
+ }: Pick<ToggleProps, "checked" | "onChange" | "label" | "disabled" | "size" | "className">) {
107
186
  const reducedMotion = useReducedMotion()
108
187
  const id = `toggle-goo-${useId().replace(/:/g, "")}`
109
- const x = useMotionValue(checked ? 22 : 2)
110
- // One position owns the thumb. The drop follows that position, not a delayed target.
188
+ const geo = switchGeo[size]
189
+ const on = geo.travel
190
+ const off = geo.inset
191
+ const x = useMotionValue(checked ? on : off)
111
192
  const chase = useSpring(x, spring.snappy)
112
- const dropX = useTransform(chase, (value) => value + 5)
193
+ const dropX = useTransform(chase, (value) => value + geo.dropOffset)
113
194
  const gesture = useRef({ start: 0, x: 0, moved: false, skipClick: false, held: false })
114
195
  useEffect(() => {
115
196
  if (reducedMotion) {
116
- x.set(checked ? 22 : 2)
197
+ x.set(checked ? on : off)
117
198
  return
118
199
  }
119
- const animation = animate(x, checked ? 22 : 2, spring.snappy)
200
+ const animation = animate(x, checked ? on : off, spring.snappy)
120
201
  return () => animation.stop()
121
- }, [checked, reducedMotion, x])
202
+ }, [checked, reducedMotion, x, on, off])
122
203
  const choose = (next: boolean) => {
123
204
  if (disabled) return
124
205
  haptic("selection")
125
206
  if (next !== checked) onChange(next)
126
- // Releasing on the current side still needs a settling spring.
127
- if (reducedMotion) x.set(next ? 22 : 2)
128
- else animate(x, next ? 22 : 2, spring.snappy)
207
+ if (reducedMotion) x.set(next ? on : off)
208
+ else animate(x, next ? on : off, spring.snappy)
129
209
  }
130
210
  return (
131
211
  <button
@@ -158,7 +238,7 @@ function SwitchToggle({
158
238
  const direction = getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1
159
239
  const delta = (event.clientX - drag.start) * direction
160
240
  if (Math.abs(delta) > 3) drag.moved = true
161
- if (drag.moved) x.set(Math.max(2, Math.min(22, drag.x + delta)))
241
+ if (drag.moved) x.set(Math.max(off, Math.min(on, drag.x + delta)))
162
242
  }}
163
243
  onPointerUp={() => {
164
244
  const drag = gesture.current
@@ -166,7 +246,7 @@ function SwitchToggle({
166
246
  drag.held = false
167
247
  if (!drag.moved) return
168
248
  drag.skipClick = true
169
- choose(x.get() >= 12)
249
+ choose(x.get() >= (on + off) / 2)
170
250
  }}
171
251
  onPointerCancel={() => {
172
252
  gesture.current.held = false
@@ -174,7 +254,8 @@ function SwitchToggle({
174
254
  choose(checked)
175
255
  }}
176
256
  className={cn(
177
- "relative h-[30px] w-[50px] shrink-0 touch-pan-y rounded-full transition-colors duration-normal motion-reduce:transition-none",
257
+ "relative shrink-0 touch-pan-y rounded-full transition-colors duration-normal motion-reduce:transition-none",
258
+ geo.track,
178
259
  "shadow-[inset_0_1px_2px_rgb(0_0_0/0.10)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/70 focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:opacity-45",
179
260
  checked ? "bg-accent" : "bg-separator/80",
180
261
  className
@@ -206,12 +287,16 @@ function SwitchToggle({
206
287
  >
207
288
  {!reducedMotion && (
208
289
  <motion.span
209
- className="absolute top-[7px] left-0 size-4 rounded-full bg-white"
290
+ className={cn(
291
+ "absolute left-0 rounded-full bg-white",
292
+ geo.dropTop,
293
+ geo.drop
294
+ )}
210
295
  style={{ x: dropX }}
211
296
  />
212
297
  )}
213
298
  <motion.span
214
- className="absolute top-0.5 left-0 size-[26px] rounded-full bg-white"
299
+ className={cn("absolute top-0.5 left-0 rounded-full bg-white", geo.thumb)}
215
300
  style={{ x }}
216
301
  />
217
302
  </span>
package/src/index.ts CHANGED
@@ -102,3 +102,5 @@ export * from "./components/wake-slider/wake-slider"
102
102
  export * from "./components/one-time-code/one-time-code"
103
103
 
104
104
  export * from "./components/aspect-ratio/aspect-ratio"
105
+
106
+ export * from "./components/morph"