@boyernick/standard-ui-react 0.1.1-canary.4 → 0.1.1-canary.6

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyernick/standard-ui-react",
3
- "version": "0.1.1-canary.4",
3
+ "version": "0.1.1-canary.6",
4
4
  "description": "React components for StandardUI",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/checkbox.tsx CHANGED
@@ -4,28 +4,50 @@ import { Checkbox as BaseCheckbox } from "@base-ui/react/checkbox"
4
4
  import type { ComponentProps } from "react"
5
5
  import { IconCheckmark1, IconMinus } from "./icons"
6
6
  import { cn } from "./lib/cn"
7
+ import { motion } from "./lib/motion"
7
8
 
8
9
  export type CheckboxProps = ComponentProps<typeof BaseCheckbox.Root>
9
10
 
10
11
  export const Checkbox = ({ className, ...props }: CheckboxProps) => (
11
12
  <BaseCheckbox.Root
12
13
  className={cn(
13
- "group flex size-4 shrink-0 items-center justify-center rounded-sm border border-border-secondary bg-surface 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:border-brand-primary data-checked:bg-brand-primary data-indeterminate:border-brand-primary data-indeterminate:bg-brand-primary data-disabled:cursor-not-allowed data-disabled:opacity-50",
14
+ "group flex size-4 shrink-0 items-center justify-center rounded-sm border border-border-secondary bg-surface outline-none",
15
+ // motion.all, not motion.colors: the filled state brings a box shadow
16
+ // with it, which has to fade in alongside the background.
17
+ motion.all,
18
+ "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20",
19
+ // Ticked and mixed are both filled controls, so they carry the primary
20
+ // button's treatment: brand fill, the border token that pairs with it,
21
+ // and the top-edge highlight that makes a solid control read as raised.
22
+ "data-checked:border-brand-primary-border data-checked:bg-brand-primary data-checked:inset-shadow-solid-top",
23
+ "data-indeterminate:border-brand-primary-border data-indeterminate:bg-brand-primary data-indeterminate:inset-shadow-solid-top",
24
+ "data-checked:hover:bg-brand-primary-hover data-indeterminate:hover:bg-brand-primary-hover",
25
+ "data-checked:active:bg-brand-primary-active data-indeterminate:active:bg-brand-primary-active",
26
+ "data-disabled:cursor-not-allowed data-disabled:opacity-50",
14
27
  className,
15
28
  )}
16
29
  {...props}
17
30
  >
18
- <BaseCheckbox.Indicator className="flex text-brand-foreground data-unchecked:hidden">
19
- <IconCheckmark1
20
- size={12}
21
- className="size-3 group-data-indeterminate:hidden"
22
- aria-hidden
23
- />
24
- <IconMinus
25
- size={12}
26
- className="hidden size-3 group-data-indeterminate:block"
27
- aria-hidden
28
- />
29
- </BaseCheckbox.Indicator>
31
+ {/* Only the active glyph is mounted. Central Icons give every instance of
32
+ an icon the same mask id, so a CSS-hidden twin would claim that id for
33
+ the whole document; a `display: none` subtree renders no mask, leaving
34
+ the visible instance to paint its backing rect unmasked as a solid
35
+ block. Mounting one icon keeps every id in the document resolvable. */}
36
+ {/* No `data-unchecked:hidden` here. Base UI keeps the indicator mounted
37
+ through its exit and flags it with both `data-unchecked` and
38
+ `data-ending-style`; hiding on the former would cut the latter's
39
+ transition short and the mark would vanish instantly. */}
40
+ <BaseCheckbox.Indicator
41
+ className={cn("flex text-brand-foreground", motion.checkIndicator)}
42
+ render={(props, state) => (
43
+ <span {...props}>
44
+ {state.indeterminate ? (
45
+ <IconMinus size={12} className="size-3" aria-hidden />
46
+ ) : (
47
+ <IconCheckmark1 size={12} className="size-3" aria-hidden />
48
+ )}
49
+ </span>
50
+ )}
51
+ />
30
52
  </BaseCheckbox.Root>
31
53
  )
@@ -25,7 +25,7 @@ export const CollapsibleTrigger = ({
25
25
  className={cn(
26
26
  "group flex h-9 w-full cursor-pointer items-center justify-between gap-2 rounded-md border border-border-secondary bg-surface px-3 text-sm text-fg-primary inset-shadow-outline-top outline-none",
27
27
  motion.colors,
28
- "hover:bg-background-tertiary 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-disabled:cursor-not-allowed data-disabled:opacity-50",
28
+ "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-disabled:cursor-not-allowed data-disabled:opacity-50",
29
29
  className,
30
30
  )}
31
31
  {...props}
@@ -56,6 +56,8 @@ export const CollapsiblePanel = ({
56
56
  )}
57
57
  {...props}
58
58
  >
59
- <div className="pt-2 leading-relaxed">{children}</div>
59
+ {/* px-3 matches the trigger's own padding, so the panel copy lines up
60
+ under the trigger's label instead of starting at the panel edge. */}
61
+ <div className="px-3 pt-2 leading-relaxed">{children}</div>
60
62
  </BaseCollapsible.Panel>
61
63
  )
package/src/combobox.tsx CHANGED
@@ -77,7 +77,6 @@ export const ComboboxInputGroup = ({
77
77
  <BaseCombobox.InputGroup
78
78
  className={cn(
79
79
  "relative flex min-h-9 w-full items-center rounded-md border border-border-secondary bg-surface inset-shadow-outline-top outline-none transition-[color,box-shadow]",
80
- "has-[[data-popup-open]]:bg-background-tertiary/60",
81
80
  "focus-within:border-ring focus-within:ring-[3px] focus-within:ring-offset-1 focus-within:ring-offset-background-primary focus-within:ring-ring/20",
82
81
  "aria-invalid:border-destructive aria-invalid:focus-within:border-destructive aria-invalid:focus-within:ring-destructive/20",
83
82
  "has-[[aria-invalid=true]]:border-destructive has-[[aria-invalid=true]]:focus-within:border-destructive has-[[aria-invalid=true]]:focus-within:ring-destructive/20",
package/src/lib/motion.ts CHANGED
@@ -14,7 +14,8 @@
14
14
  *
15
15
  * Two kinds of token live here, and they behave differently:
16
16
  *
17
- * Mount tokens — backdrop, popupCenter, popupAnchor, accordionPanel.
17
+ * Mount tokens — backdrop, popupCenter, popupAnchor, accordionPanel,
18
+ * checkIndicator.
18
19
  * These carry separate enter and exit behaviour. Base UI flags the exit
19
20
  * with `data-ending-style`, and the `data-ending-style:` variants below
20
21
  * re-declare duration and easing for that direction. Exits always run at
@@ -91,6 +92,20 @@ export const motion = {
91
92
  "data-ending-style:ease-move",
92
93
  ].join(" "),
93
94
 
95
+ /** Check and minus glyphs mounting inside a small control. Snaps, because
96
+ * it answers a click directly, and scales from the centre so the mark
97
+ * reads as landing in the box rather than fading onto it. */
98
+ checkIndicator: [
99
+ "transition-[scale,opacity]",
100
+ DURATION.sm,
101
+ "ease-snap",
102
+ REDUCED,
103
+ "data-starting-style:scale-50 data-starting-style:opacity-0",
104
+ "data-ending-style:scale-50 data-ending-style:opacity-0",
105
+ EXIT_DURATION,
106
+ "data-ending-style:ease-move",
107
+ ].join(" "),
108
+
94
109
  /** Sliding tab underline or pill — follows a pointer, so it snaps. */
95
110
  tabsIndicator: [
96
111
  "transition-[translate,width]",
package/src/select.tsx CHANGED
@@ -33,7 +33,7 @@ export const SelectTrigger = ({
33
33
  className={cn(
34
34
  "flex h-9 w-full cursor-pointer items-center justify-between gap-2 rounded-md border border-border-secondary bg-surface px-3 text-sm text-fg-primary inset-shadow-outline-top outline-none",
35
35
  motion.all,
36
- "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 data-disabled:cursor-not-allowed data-disabled:opacity-50 data-placeholder:text-fg-quaternary data-popup-open:border-border-secondary data-popup-open:bg-background-tertiary/60",
36
+ "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 data-disabled:cursor-not-allowed data-disabled:opacity-50 data-placeholder:text-fg-quaternary data-popup-open:border-border-secondary",
37
37
  className,
38
38
  )}
39
39
  {...props}