@boyernick/standard-ui-react 0.1.1-canary.2 → 0.1.1-canary.4
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/package.json +1 -1
- package/src/accordion.tsx +1 -1
- package/src/autocomplete.tsx +10 -11
- package/src/badge.tsx +73 -9
- package/src/breadcrumb.tsx +39 -21
- package/src/button.tsx +2 -2
- package/src/calendar.tsx +22 -11
- package/src/card.tsx +57 -14
- package/src/carousel.tsx +163 -6
- package/src/checkbox.tsx +1 -1
- package/src/date-picker.tsx +169 -0
- package/src/drawer.tsx +1 -1
- package/src/field.tsx +1 -1
- package/src/icons.tsx +27 -0
- package/src/index.ts +18 -0
- package/src/input.tsx +1 -1
- package/src/lib/motion.ts +113 -18
- package/src/menu.tsx +3 -3
- package/src/meter.tsx +1 -1
- package/src/navigation-menu.tsx +2 -2
- package/src/progress.tsx +1 -1
- package/src/scroll-area.tsx +1 -1
- package/src/slider.tsx +1 -1
- package/src/switch.tsx +2 -2
- package/src/text-animate.tsx +2 -2
- package/src/textarea.tsx +1 -1
- package/src/toast.tsx +2 -2
package/src/lib/motion.ts
CHANGED
|
@@ -1,23 +1,118 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared motion classes for StandardUI.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Every value resolves to a token in `@boyernick/standard-ui-tokens` — the
|
|
5
|
+
* `--ease-*` ladder and the `--duration-*` scale. Nothing here hardcodes a
|
|
6
|
+
* curve or a millisecond count.
|
|
7
|
+
*
|
|
8
|
+
* The easing ladder is ordered by *onset* — how quickly a transition commits:
|
|
9
|
+
*
|
|
10
|
+
* passive ambient, system-initiated (slowest onset)
|
|
11
|
+
* enter surfaces arriving
|
|
12
|
+
* move things travelling across a surface
|
|
13
|
+
* snap direct response to a pointer (sharpest onset)
|
|
14
|
+
*
|
|
15
|
+
* Two kinds of token live here, and they behave differently:
|
|
16
|
+
*
|
|
17
|
+
* Mount tokens — backdrop, popupCenter, popupAnchor, accordionPanel.
|
|
18
|
+
* These carry separate enter and exit behaviour. Base UI flags the exit
|
|
19
|
+
* with `data-ending-style`, and the `data-ending-style:` variants below
|
|
20
|
+
* re-declare duration and easing for that direction. Exits always run at
|
|
21
|
+
* `--duration-exit`, so dismissing anything costs the same no matter how
|
|
22
|
+
* long the entrance took.
|
|
23
|
+
*
|
|
24
|
+
* State tokens — tabsIndicator, colors, transform, all.
|
|
25
|
+
* These animate between states on a mounted element, so there is no enter
|
|
26
|
+
* or exit to distinguish; they take a single curve.
|
|
27
|
+
*
|
|
28
|
+
* Fades are linear on purpose. Perceived brightness is already non-linear, so
|
|
29
|
+
* easing an opacity change makes it look like it sticks at the end.
|
|
5
30
|
*/
|
|
31
|
+
|
|
32
|
+
const DURATION = {
|
|
33
|
+
xs: "duration-[var(--duration-xs)]",
|
|
34
|
+
sm: "duration-[var(--duration-sm)]",
|
|
35
|
+
md: "duration-[var(--duration-md)]",
|
|
36
|
+
lg: "duration-[var(--duration-lg)]",
|
|
37
|
+
} as const
|
|
38
|
+
|
|
39
|
+
/** Every exit runs at the same length — see `--duration-exit`. */
|
|
40
|
+
const EXIT_DURATION = "data-ending-style:duration-[var(--duration-exit)]"
|
|
41
|
+
|
|
42
|
+
const REDUCED = "motion-reduce:transition-none"
|
|
43
|
+
|
|
6
44
|
export const motion = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"transition-
|
|
22
|
-
|
|
45
|
+
/** Overlay scrims. Linear both ways — see the note on fades above. */
|
|
46
|
+
backdrop: [
|
|
47
|
+
"transition-opacity",
|
|
48
|
+
DURATION.sm,
|
|
49
|
+
"ease-linear",
|
|
50
|
+
REDUCED,
|
|
51
|
+
"data-starting-style:opacity-0",
|
|
52
|
+
"data-ending-style:opacity-0",
|
|
53
|
+
EXIT_DURATION,
|
|
54
|
+
"data-ending-style:ease-linear",
|
|
55
|
+
].join(" "),
|
|
56
|
+
|
|
57
|
+
/** Centred dialog / alert — animates `scale`, so translate centring holds. */
|
|
58
|
+
popupCenter: [
|
|
59
|
+
"transition-[scale,opacity]",
|
|
60
|
+
DURATION.md,
|
|
61
|
+
"ease-enter",
|
|
62
|
+
REDUCED,
|
|
63
|
+
"data-starting-style:scale-[0.96] data-starting-style:opacity-0",
|
|
64
|
+
"data-ending-style:scale-[0.96] data-ending-style:opacity-0",
|
|
65
|
+
EXIT_DURATION,
|
|
66
|
+
"data-ending-style:ease-move",
|
|
67
|
+
].join(" "),
|
|
68
|
+
|
|
69
|
+
/** Select, tooltip, menus — transform-origin comes from Base UI. */
|
|
70
|
+
popupAnchor: [
|
|
71
|
+
"origin-[var(--transform-origin)]",
|
|
72
|
+
"transition-[transform,scale,opacity]",
|
|
73
|
+
DURATION.sm,
|
|
74
|
+
"ease-enter",
|
|
75
|
+
REDUCED,
|
|
76
|
+
"data-starting-style:scale-[0.96] data-starting-style:opacity-0",
|
|
77
|
+
"data-ending-style:scale-[0.96] data-ending-style:opacity-0",
|
|
78
|
+
EXIT_DURATION,
|
|
79
|
+
"data-ending-style:ease-move",
|
|
80
|
+
].join(" "),
|
|
81
|
+
|
|
82
|
+
/** Height expand and collapse. */
|
|
83
|
+
accordionPanel: [
|
|
84
|
+
"transition-[height]",
|
|
85
|
+
DURATION.md,
|
|
86
|
+
"ease-move",
|
|
87
|
+
REDUCED,
|
|
88
|
+
"data-starting-style:h-0",
|
|
89
|
+
"data-ending-style:h-0",
|
|
90
|
+
EXIT_DURATION,
|
|
91
|
+
"data-ending-style:ease-move",
|
|
92
|
+
].join(" "),
|
|
93
|
+
|
|
94
|
+
/** Sliding tab underline or pill — follows a pointer, so it snaps. */
|
|
95
|
+
tabsIndicator: [
|
|
96
|
+
"transition-[translate,width]",
|
|
97
|
+
DURATION.md,
|
|
98
|
+
"ease-snap",
|
|
99
|
+
REDUCED,
|
|
100
|
+
].join(" "),
|
|
101
|
+
|
|
102
|
+
/** Hover and pressed colour changes. */
|
|
103
|
+
colors: ["transition-colors", DURATION.sm, "ease-enter", REDUCED].join(" "),
|
|
104
|
+
|
|
105
|
+
/** Icon rotates and simple transforms. */
|
|
106
|
+
transform: [
|
|
107
|
+
"transition-transform",
|
|
108
|
+
DURATION.sm,
|
|
109
|
+
"ease-move",
|
|
110
|
+
REDUCED,
|
|
111
|
+
].join(" "),
|
|
112
|
+
|
|
113
|
+
/** Every property. Reach for a narrower token first. */
|
|
114
|
+
all: ["transition-all", DURATION.sm, "ease-enter", REDUCED].join(" "),
|
|
115
|
+
|
|
116
|
+
/** Long, ambient movement — progress fills, meters, background shifts. */
|
|
117
|
+
passive: ["transition-all", DURATION.lg, "ease-passive", REDUCED].join(" "),
|
|
23
118
|
} as const
|
package/src/menu.tsx
CHANGED
|
@@ -34,7 +34,7 @@ export type MenuSubmenuTriggerProps = ComponentProps<
|
|
|
34
34
|
export type MenuViewportProps = ComponentProps<typeof BaseMenu.Viewport>
|
|
35
35
|
|
|
36
36
|
const menuItemClassName = cn(
|
|
37
|
-
"flex
|
|
37
|
+
"flex h-8 cursor-default items-center gap-2 rounded-sm px-2.5 py-1.5 text-sm text-fg-primary outline-none select-none",
|
|
38
38
|
motion.colors,
|
|
39
39
|
"data-disabled:cursor-not-allowed data-disabled:opacity-50 data-highlighted:bg-background-tertiary",
|
|
40
40
|
)
|
|
@@ -59,7 +59,7 @@ export const MenuBackdrop = ({ className, ...props }: MenuBackdropProps) => (
|
|
|
59
59
|
)
|
|
60
60
|
|
|
61
61
|
export const MenuPositioner = ({
|
|
62
|
-
sideOffset =
|
|
62
|
+
sideOffset = 8,
|
|
63
63
|
className,
|
|
64
64
|
...props
|
|
65
65
|
}: MenuPositionerProps) => (
|
|
@@ -73,7 +73,7 @@ export const MenuPositioner = ({
|
|
|
73
73
|
export const MenuPopup = ({ className, ...props }: MenuPopupProps) => (
|
|
74
74
|
<BaseMenu.Popup
|
|
75
75
|
className={cn(
|
|
76
|
-
"z-50 min-w-40 overflow-hidden rounded-
|
|
76
|
+
"z-50 min-w-40 overflow-hidden rounded-xl border border-[var(--border-faint)] bg-surface p-1.5 shadow-lg outline-none",
|
|
77
77
|
motion.popupAnchor,
|
|
78
78
|
className,
|
|
79
79
|
)}
|
package/src/meter.tsx
CHANGED
|
@@ -53,7 +53,7 @@ export const MeterIndicator = ({
|
|
|
53
53
|
}: MeterIndicatorProps) => (
|
|
54
54
|
<BaseMeter.Indicator
|
|
55
55
|
className={cn(
|
|
56
|
-
"h-full rounded-full bg-brand-primary transition-[width] duration-
|
|
56
|
+
"h-full rounded-full bg-brand-primary transition-[width] duration-[var(--duration-lg)] ease-enter motion-reduce:transition-none",
|
|
57
57
|
className,
|
|
58
58
|
)}
|
|
59
59
|
{...props}
|
package/src/navigation-menu.tsx
CHANGED
|
@@ -101,7 +101,7 @@ export const NavigationMenuIcon = ({
|
|
|
101
101
|
}: NavigationMenuIconProps) => (
|
|
102
102
|
<BaseNavigationMenu.Icon
|
|
103
103
|
className={cn(
|
|
104
|
-
"inline-flex text-fg-tertiary transition-transform duration-
|
|
104
|
+
"inline-flex text-fg-tertiary transition-transform duration-[var(--duration-sm)] ease-enter motion-reduce:transition-none data-popup-open:rotate-180",
|
|
105
105
|
className,
|
|
106
106
|
)}
|
|
107
107
|
{...props}
|
|
@@ -178,7 +178,7 @@ export const NavigationMenuPopup = ({
|
|
|
178
178
|
<BaseNavigationMenu.Popup
|
|
179
179
|
className={cn(
|
|
180
180
|
"relative z-50 w-[var(--popup-width)] overflow-hidden rounded-xl border border-border-primary bg-surface shadow-md outline-none",
|
|
181
|
-
"h-[var(--popup-height)] transition-[width,height,opacity] duration-
|
|
181
|
+
"h-[var(--popup-height)] transition-[width,height,opacity] duration-[var(--duration-md)] ease-enter motion-reduce:transition-none",
|
|
182
182
|
"data-starting-style:opacity-0 data-ending-style:opacity-0",
|
|
183
183
|
className,
|
|
184
184
|
)}
|
package/src/progress.tsx
CHANGED
|
@@ -52,7 +52,7 @@ export const ProgressIndicator = ({
|
|
|
52
52
|
}: ProgressIndicatorProps) => (
|
|
53
53
|
<BaseProgress.Indicator
|
|
54
54
|
className={cn(
|
|
55
|
-
"h-full rounded-full bg-brand-primary transition-[width] duration-
|
|
55
|
+
"h-full rounded-full bg-brand-primary transition-[width] duration-[var(--duration-lg)] ease-enter motion-reduce:transition-none",
|
|
56
56
|
className,
|
|
57
57
|
)}
|
|
58
58
|
{...props}
|
package/src/scroll-area.tsx
CHANGED
|
@@ -49,7 +49,7 @@ export const ScrollAreaScrollbar = ({
|
|
|
49
49
|
}: ScrollAreaScrollbarProps) => (
|
|
50
50
|
<BaseScrollArea.Scrollbar
|
|
51
51
|
className={cn(
|
|
52
|
-
"flex touch-none p-0.5 opacity-100 transition-opacity duration-
|
|
52
|
+
"flex touch-none p-0.5 opacity-100 transition-opacity duration-[var(--duration-sm)] ease-enter motion-reduce:transition-none data-[orientation=horizontal]:h-2 data-[orientation=vertical]:w-2",
|
|
53
53
|
className,
|
|
54
54
|
)}
|
|
55
55
|
{...props}
|
package/src/slider.tsx
CHANGED
|
@@ -38,7 +38,7 @@ export const SliderIndicator = ({
|
|
|
38
38
|
}: SliderIndicatorProps) => (
|
|
39
39
|
<BaseSlider.Indicator
|
|
40
40
|
className={cn(
|
|
41
|
-
"absolute h-full rounded-full bg-brand-primary transition-[width] duration-
|
|
41
|
+
"absolute h-full rounded-full bg-brand-primary transition-[width] duration-[var(--duration-sm)] ease-enter motion-reduce:transition-none",
|
|
42
42
|
className,
|
|
43
43
|
)}
|
|
44
44
|
{...props}
|
package/src/switch.tsx
CHANGED
|
@@ -9,11 +9,11 @@ export type SwitchProps = ComponentProps<typeof BaseSwitch.Root>
|
|
|
9
9
|
export const Switch = ({ className, ...props }: SwitchProps) => (
|
|
10
10
|
<BaseSwitch.Root
|
|
11
11
|
className={cn(
|
|
12
|
-
"relative inline-flex h-6 w-10 shrink-0 items-center rounded-full bg-background-quaternary transition-colors duration-
|
|
12
|
+
"relative inline-flex h-6 w-10 shrink-0 items-center rounded-full bg-background-quaternary transition-colors duration-[var(--duration-sm)] ease-enter motion-reduce:transition-none outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20 data-checked:bg-brand-primary data-disabled:cursor-not-allowed data-disabled:opacity-50",
|
|
13
13
|
className,
|
|
14
14
|
)}
|
|
15
15
|
{...props}
|
|
16
16
|
>
|
|
17
|
-
<BaseSwitch.Thumb className="pointer-events-none block size-5 translate-x-0.5 rounded-full bg-surface shadow-sm transition-transform duration-
|
|
17
|
+
<BaseSwitch.Thumb className="pointer-events-none block size-5 translate-x-0.5 rounded-full bg-surface shadow-sm transition-transform duration-[var(--duration-sm)] ease-enter motion-reduce:transition-none data-checked:translate-x-[1.125rem]" />
|
|
18
18
|
</BaseSwitch.Root>
|
|
19
19
|
)
|
package/src/text-animate.tsx
CHANGED
|
@@ -122,9 +122,9 @@ export const TextAnimate = ({
|
|
|
122
122
|
className={cn(
|
|
123
123
|
"inline-block text-fg-primary",
|
|
124
124
|
effect === "fade" &&
|
|
125
|
-
"animate-[text-fade-in_0.
|
|
125
|
+
"animate-[text-fade-in_0.6s_var(--ease-enter)_both] motion-reduce:animate-none",
|
|
126
126
|
effect === "blur" &&
|
|
127
|
-
"animate-[text-blur-in_0.
|
|
127
|
+
"animate-[text-blur-in_0.7s_var(--ease-enter)_both] motion-reduce:animate-none",
|
|
128
128
|
className,
|
|
129
129
|
)}
|
|
130
130
|
style={{ animationDelay: `${delay}ms` }}
|
package/src/textarea.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import type { TextareaHTMLAttributes } from "react"
|
|
|
3
3
|
import { cn } from "./lib/cn"
|
|
4
4
|
|
|
5
5
|
const textareaVariants = cva(
|
|
6
|
-
"flex min-h-20 w-full cursor-text resize-y rounded-md px-3 py-2 text-sm text-fg-primary transition-[color,box-shadow] duration-
|
|
6
|
+
"flex min-h-20 w-full cursor-text resize-y rounded-md px-3 py-2 text-sm text-fg-primary transition-[color,box-shadow] duration-[var(--duration-sm)] ease-enter placeholder:text-fg-quaternary outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20 aria-invalid:border-destructive aria-invalid:focus-visible:border-destructive aria-invalid:focus-visible:ring-destructive/20 disabled:cursor-not-allowed disabled:opacity-50",
|
|
7
7
|
{
|
|
8
8
|
variants: {
|
|
9
9
|
variant: {
|
package/src/toast.tsx
CHANGED
|
@@ -41,7 +41,7 @@ export const ToastRoot = ({ className, ...props }: ToastRootProps) => (
|
|
|
41
41
|
className={cn(
|
|
42
42
|
"relative z-[calc(1000-var(--toast-index))] mb-2 box-border w-full rounded-xl border border-border-primary bg-surface p-4 shadow-lg outline-none",
|
|
43
43
|
"h-[var(--toast-frontmost-height,var(--toast-height))]",
|
|
44
|
-
"transition-[transform,opacity] duration-
|
|
44
|
+
"transition-[transform,opacity] duration-[var(--duration-md)] ease-enter motion-reduce:transition-none",
|
|
45
45
|
"data-starting-style:opacity-0 data-starting-style:translate-y-2",
|
|
46
46
|
"data-ending-style:opacity-0 data-ending-style:translate-y-2",
|
|
47
47
|
"data-expanded:h-[var(--toast-height)] data-expanded:translate-y-[var(--toast-offset-y)]",
|
|
@@ -56,7 +56,7 @@ export const ToastRoot = ({ className, ...props }: ToastRootProps) => (
|
|
|
56
56
|
export const ToastContent = ({ className, ...props }: ToastContentProps) => (
|
|
57
57
|
<BaseToast.Content
|
|
58
58
|
className={cn(
|
|
59
|
-
"flex flex-col gap-1 overflow-hidden transition-opacity duration-
|
|
59
|
+
"flex flex-col gap-1 overflow-hidden transition-opacity duration-[var(--duration-sm)]",
|
|
60
60
|
"data-behind:pointer-events-none data-behind:opacity-0 data-expanded:data-behind:opacity-100",
|
|
61
61
|
className,
|
|
62
62
|
)}
|