@instruments/materia-ui 0.2.0 → 0.3.1
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/dist/components/animated-number/number-digit.d.ts +5 -3
- package/dist/components/animated-number/number-digit.js +39 -16
- package/dist/components/animated-number/number-section.d.ts +7 -5
- package/dist/components/animated-number/number-section.js +81 -65
- package/dist/components/animated-number/number-symbol.js +2 -0
- package/dist/components/animated-number/target-widths.d.ts +7 -0
- package/dist/components/animated-number/target-widths.js +13 -1
- package/dist/components/button/button-variants.d.ts +2 -1
- package/dist/components/button/button-variants.js +2 -1
- package/dist/components/pill/pill-variants.d.ts +3 -0
- package/dist/components/pill/pill-variants.js +8 -1
- package/dist/components/segmented-control/segmented-control-indicator.js +1 -1
- package/dist/components/segmented-control/segmented-control-item.js +1 -1
- package/dist/components/segmented-control/segmented-control-root.js +1 -1
- package/dist/components/segmented-control/segmented-control-variants.js +1 -1
- package/dist/components/stepper/stepper-item.js +6 -2
- package/dist/components/stepper/stepper-variants.d.ts +1 -0
- package/dist/components/stepper/stepper-variants.js +11 -1
- package/dist/components/tab-navigation/tab-navigation-link.js +1 -1
- package/dist/components/tabs/tabs-indicator.js +1 -1
- package/dist/components/tabs/tabs-trigger.js +1 -1
- package/dist/components/tabs/tabs-variants.js +1 -1
- package/dist/data/docs-manifest.d.ts +1 -1
- package/dist/data/docs-manifest.js +5 -50
- package/dist/data/token-gallery.js +1 -1
- package/dist/styles/index.css +7 -1
- package/dist/tokens/emphasis.d.ts +5 -0
- package/dist/tokens/emphasis.js +5 -0
- package/dist/{components/segmented/segmented-variants.d.ts → tokens/segmented.d.ts} +12 -1
- package/dist/{components/segmented/segmented-variants.js → tokens/segmented.js} +11 -0
- package/package.json +5 -5
- package/dist/components/segmented/segmented-root.d.ts +0 -12
- package/dist/components/segmented/segmented-root.js +0 -12
- package/dist/components/segmented.d.ts +0 -1
- package/dist/components/segmented.js +0 -1
|
@@ -15,10 +15,12 @@ interface NumberDigitProps extends HTMLMotionProps<"span"> {
|
|
|
15
15
|
* through every intermediate numeral in either direction, including across the
|
|
16
16
|
* 9/0 wrap when `trend` asks for it. New leading digits enter with
|
|
17
17
|
* `initialValue={0}` and roll up from zero; exiting digits roll to zero so
|
|
18
|
-
* removal also animates.
|
|
19
|
-
* proportional fonts stay aligned
|
|
18
|
+
* removal also animates. The column's real width tweens to the target glyph
|
|
19
|
+
* width on the roll's timing, so proportional fonts stay aligned without a
|
|
20
|
+
* one-frame jump.
|
|
20
21
|
*
|
|
21
|
-
* Under reduced motion the FLIP
|
|
22
|
+
* Under reduced motion the FLIP and the width tween are skipped: the digit
|
|
23
|
+
* snaps to its value and width.
|
|
22
24
|
*/
|
|
23
25
|
export declare const NumberDigit: ({ value: _value, trend, ...rest }: NumberDigitProps) => import("react").JSX.Element;
|
|
24
26
|
export {};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { animate, MotionConfigContext, PresenceContext, useIsPresent, useReducedMotion, } from "motion/react";
|
|
3
|
+
import { animate, MotionConfigContext, PresenceContext, useIsPresent, useMotionValue, useReducedMotion, } from "motion/react";
|
|
4
4
|
import * as m from "motion/react-m";
|
|
5
|
-
import { use, useEffect, useLayoutEffect, useRef
|
|
5
|
+
import { use, useEffect, useLayoutEffect, useRef } from "react";
|
|
6
6
|
import { getWidthInEm } from "./get-width-in-em.js";
|
|
7
7
|
import { maskHeight } from "./mask.js";
|
|
8
|
-
import { targetWidths } from "./target-widths.js";
|
|
8
|
+
import { targetWidths, widthTiming } from "./target-widths.js";
|
|
9
9
|
const mod = (n, base) => ((n % base) + base) % base;
|
|
10
10
|
/** The nine digits above and below `value`, wrapping mod 10. */
|
|
11
11
|
const ABOVE_OFFSETS = [9, 8, 7, 6, 5, 4, 3, 2, 1];
|
|
@@ -24,10 +24,12 @@ const digitFillStyle = {
|
|
|
24
24
|
* through every intermediate numeral in either direction, including across the
|
|
25
25
|
* 9/0 wrap when `trend` asks for it. New leading digits enter with
|
|
26
26
|
* `initialValue={0}` and roll up from zero; exiting digits roll to zero so
|
|
27
|
-
* removal also animates.
|
|
28
|
-
* proportional fonts stay aligned
|
|
27
|
+
* removal also animates. The column's real width tweens to the target glyph
|
|
28
|
+
* width on the roll's timing, so proportional fonts stay aligned without a
|
|
29
|
+
* one-frame jump.
|
|
29
30
|
*
|
|
30
|
-
* Under reduced motion the FLIP
|
|
31
|
+
* Under reduced motion the FLIP and the width tween are skipped: the digit
|
|
32
|
+
* snaps to its value and width.
|
|
31
33
|
*/
|
|
32
34
|
export const NumberDigit = ({ value: _value, trend = 0, ...rest }) => {
|
|
33
35
|
const { transition } = use(MotionConfigContext);
|
|
@@ -45,15 +47,23 @@ export const NumberDigit = ({ value: _value, trend = 0, ...rest }) => {
|
|
|
45
47
|
// Exit isn't a normal AnimatePresence exit because it must trigger a resize.
|
|
46
48
|
const isPresent = useIsPresent();
|
|
47
49
|
const value = isPresent ? _value : 0;
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
+
// The column's real layout width, tweened to each value's glyph width so a
|
|
51
|
+
// proportional font never jumps its neighbours. Natural until mounted.
|
|
52
|
+
const width = useMotionValue("auto");
|
|
53
|
+
// Set the width to the initial value's width before paint, so the first
|
|
54
|
+
// change tweens from it.
|
|
50
55
|
useLayoutEffect(() => {
|
|
51
56
|
const initialEl = numberRefs.current[initialValueRef.current];
|
|
52
57
|
if (!scope.current || !initialEl) {
|
|
53
58
|
return;
|
|
54
59
|
}
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
const initialWidth = getWidthInEm(initialEl);
|
|
61
|
+
scope.current.style.width = initialWidth;
|
|
62
|
+
width.jump(initialWidth);
|
|
63
|
+
}, [width]);
|
|
64
|
+
useEffect(() => () => {
|
|
65
|
+
width.stop();
|
|
66
|
+
}, [width]);
|
|
57
67
|
// FLIP the y in a layout effect. initialY is the pixel offset from the new
|
|
58
68
|
// resting position, including any in-flight offset (inner vs outer rect top)
|
|
59
69
|
// so mid-roll re-targets stay continuous.
|
|
@@ -83,24 +93,37 @@ export const NumberDigit = ({ value: _value, trend = 0, ...rest }) => {
|
|
|
83
93
|
// (equivalent to the original's cleanup, without a returned teardown).
|
|
84
94
|
prevValue.current = value;
|
|
85
95
|
}, [value, trend, shouldReduceMotion, transition]);
|
|
86
|
-
const
|
|
96
|
+
const sizedValue = useRef(null);
|
|
87
97
|
useEffect(() => {
|
|
88
98
|
const el = numberRefs.current[value];
|
|
89
|
-
if (!el) {
|
|
99
|
+
if (!el || sizedValue.current === value) {
|
|
90
100
|
return;
|
|
91
101
|
}
|
|
102
|
+
sizedValue.current = value;
|
|
92
103
|
const w = getWidthInEm(el);
|
|
93
104
|
// Store the target width immediately, so the section resize can use it.
|
|
94
105
|
if (ref.current) {
|
|
95
106
|
targetWidths.set(ref.current, w);
|
|
96
107
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
108
|
+
if (shouldReduceMotion === true || width.get() === w) {
|
|
109
|
+
width.jump(w);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// A change mid-tween retargets from the width it has reached.
|
|
113
|
+
animate(width, w, widthTiming(transition));
|
|
114
|
+
}, [value, shouldReduceMotion, transition, width]);
|
|
100
115
|
const renderNumber = (digit) => (_jsx("span", { ref: (r) => {
|
|
101
116
|
numberRefs.current[digit] = r;
|
|
102
117
|
}, style: { display: "inline-block", padding: `calc(${maskHeight}/2) 0` }, children: digit }, digit));
|
|
103
|
-
return (_jsx(m.span, { ...rest, "data-state": isPresent ? undefined : "exiting", layout: "position", ref: ref,
|
|
118
|
+
return (_jsx(m.span, { ...rest, "data-state": isPresent ? undefined : "exiting", layout: "position", ref: ref,
|
|
119
|
+
// Never shrinks: while the section's width tweens, the glyphs overflow
|
|
120
|
+
// it at their own widths rather than squeezing.
|
|
121
|
+
style: {
|
|
122
|
+
display: "inline-flex",
|
|
123
|
+
flexShrink: 0,
|
|
124
|
+
justifyContent: "center",
|
|
125
|
+
width,
|
|
126
|
+
}, children: _jsxs("span", { ref: scope, style: {
|
|
104
127
|
alignItems: "center",
|
|
105
128
|
display: "inline-flex",
|
|
106
129
|
flexDirection: "column",
|
|
@@ -12,11 +12,13 @@ interface NumberSectionProps extends HTMLMotionProps<"span"> {
|
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
14
|
* A run of digits/symbols (pre, integer, fraction, post) sharing one anchor.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
15
|
+
*
|
|
16
|
+
* Its real layout width is a Motion value: when the glyphs change it measures
|
|
17
|
+
* where the width will settle (exiting glyphs removed, digits at their target
|
|
18
|
+
* widths) and tweens to it on the roll's timing, so nothing laid out around the
|
|
19
|
+
* number jumps when a digit, separator, prefix or suffix arrives or leaves. In
|
|
20
|
+
* the meantime the glyphs hold to the section's anchor edge and overflow the
|
|
21
|
+
* other, which the digit mask clips. Under reduced motion the width snaps.
|
|
20
22
|
*/
|
|
21
23
|
export declare const NumberSection: ({ parts, justify, mode, style, name, layoutDependency, trend, ...rest }: NumberSectionProps) => import("react").JSX.Element;
|
|
22
24
|
export {};
|
|
@@ -1,93 +1,109 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
// Port of Motion+ AnimateNumber (motion-plus 2.0.0).
|
|
3
|
-
"use client";
|
|
4
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
5
|
-
import { AnimatePresence } from "motion/react";
|
|
3
|
+
import { animate, AnimatePresence, MotionConfigContext, useMotionValue, useReducedMotion, } from "motion/react";
|
|
6
4
|
import * as m from "motion/react-m";
|
|
7
|
-
import { useEffect, useMemo, useRef
|
|
5
|
+
import { use, useEffect, useMemo, useRef } from "react";
|
|
8
6
|
import { isDigitPart } from "./format-parts.js";
|
|
9
7
|
import { getWidthInEm } from "./get-width-in-em.js";
|
|
10
8
|
import { NumberDigit } from "./number-digit.js";
|
|
11
9
|
import { NumberSymbol } from "./number-symbol.js";
|
|
12
10
|
import { SectionContext } from "./section-context.js";
|
|
13
|
-
import { targetWidths } from "./target-widths.js";
|
|
11
|
+
import { targetWidths, widthTiming } from "./target-widths.js";
|
|
12
|
+
/**
|
|
13
|
+
* The section's natural width once the change lands, in `em`: exiting glyphs
|
|
14
|
+
* removed and each digit at its target width, measured at `max-content` and
|
|
15
|
+
* then put back as it was, in one synchronous pass so nothing paints between.
|
|
16
|
+
*/
|
|
17
|
+
const measureTargetWidth = (section) => {
|
|
18
|
+
const undos = [];
|
|
19
|
+
// A static list: the loop removes children as it goes.
|
|
20
|
+
for (const child of section.querySelectorAll(":scope > *")) {
|
|
21
|
+
if (!(child instanceof HTMLElement)) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (child.dataset.state === "exiting") {
|
|
25
|
+
const next = child.nextSibling;
|
|
26
|
+
child.remove();
|
|
27
|
+
undos.push(() => {
|
|
28
|
+
if (next === null) {
|
|
29
|
+
section.append(child);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
next.before(child);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const target = targetWidths.get(child);
|
|
38
|
+
if (target === undefined) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const current = child.style.width;
|
|
42
|
+
child.style.width = target;
|
|
43
|
+
undos.push(() => {
|
|
44
|
+
child.style.width = current;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const held = section.style.width;
|
|
48
|
+
section.style.width = "max-content";
|
|
49
|
+
const width = getWidthInEm(section);
|
|
50
|
+
section.style.width = held;
|
|
51
|
+
for (let i = undos.length - 1; i >= 0; i -= 1) {
|
|
52
|
+
undos[i]?.();
|
|
53
|
+
}
|
|
54
|
+
return width;
|
|
55
|
+
};
|
|
14
56
|
/**
|
|
15
57
|
* A run of digits/symbols (pre, integer, fraction, post) sharing one anchor.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
58
|
+
*
|
|
59
|
+
* Its real layout width is a Motion value: when the glyphs change it measures
|
|
60
|
+
* where the width will settle (exiting glyphs removed, digits at their target
|
|
61
|
+
* widths) and tweens to it on the roll's timing, so nothing laid out around the
|
|
62
|
+
* number jumps when a digit, separator, prefix or suffix arrives or leaves. In
|
|
63
|
+
* the meantime the glyphs hold to the section's anchor edge and overflow the
|
|
64
|
+
* other, which the digit mask clips. Under reduced motion the width snaps.
|
|
21
65
|
*/
|
|
22
66
|
export const NumberSection = ({ parts, justify = "left", mode, style, name, layoutDependency, trend, ...rest }) => {
|
|
23
67
|
const ref = useRef(null);
|
|
24
68
|
const context = useMemo(() => ({ justify }), [justify]);
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
//
|
|
28
|
-
const
|
|
69
|
+
const { transition } = use(MotionConfigContext);
|
|
70
|
+
const shouldReduceMotion = useReducedMotion();
|
|
71
|
+
// Natural until mounted, then always an `em` length so it can tween.
|
|
72
|
+
const width = useMotionValue("auto");
|
|
29
73
|
const partsSignature = parts.map((p) => p.value).join("");
|
|
30
|
-
// On mount, fix the width to the first measured width.
|
|
31
74
|
useEffect(() => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
ref.current.style.width = getWidthInEm(measured);
|
|
75
|
+
if (ref.current) {
|
|
76
|
+
width.set(getWidthInEm(ref.current));
|
|
35
77
|
}
|
|
36
|
-
|
|
78
|
+
return () => {
|
|
79
|
+
width.stop();
|
|
80
|
+
};
|
|
81
|
+
}, [width]);
|
|
37
82
|
const measuredSignature = useRef(partsSignature);
|
|
38
83
|
useEffect(() => {
|
|
39
|
-
const
|
|
40
|
-
if (!
|
|
84
|
+
const section = ref.current;
|
|
85
|
+
if (!section || measuredSignature.current === partsSignature) {
|
|
41
86
|
return;
|
|
42
87
|
}
|
|
43
88
|
measuredSignature.current = partsSignature;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// Snapshot the children first - we mutate the live collection below.
|
|
49
|
-
const children = [...measured.children];
|
|
50
|
-
for (const child of children) {
|
|
51
|
-
if (!(child instanceof HTMLElement)) {
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
if (child.dataset.state === "exiting") {
|
|
55
|
-
const next = child.nextSibling;
|
|
56
|
-
child.remove();
|
|
57
|
-
undos.push(() => {
|
|
58
|
-
// insertBefore() appends if next is null.
|
|
59
|
-
measuredRef.current?.insertBefore(child, next);
|
|
60
|
-
});
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
const newWidth = targetWidths.get(child);
|
|
64
|
-
if (newWidth === undefined) {
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
const oldWidth = child.style.width;
|
|
68
|
-
child.style.width = newWidth;
|
|
69
|
-
undos.push(() => {
|
|
70
|
-
child.style.width = oldWidth;
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
// Measure the resulting width, then undo immediately. This is a
|
|
74
|
-
// measure-after-render pass (DOM already committed), not a render-phase
|
|
75
|
-
// adjustment, so the section can hold a fixed width during the animation.
|
|
76
|
-
setWidth(getWidthInEm(measured));
|
|
77
|
-
for (let i = undos.length - 1; i >= 0; i -= 1) {
|
|
78
|
-
undos[i]?.();
|
|
89
|
+
const target = measureTargetWidth(section);
|
|
90
|
+
if (shouldReduceMotion === true) {
|
|
91
|
+
width.jump(target);
|
|
92
|
+
return;
|
|
79
93
|
}
|
|
80
|
-
|
|
81
|
-
|
|
94
|
+
// A change mid-tween retargets from the width it has reached. Not stopped
|
|
95
|
+
// when the effect re-runs: a fresh transition object from the caller must
|
|
96
|
+
// not freeze the width part-way.
|
|
97
|
+
animate(width, target, widthTiming(transition));
|
|
98
|
+
}, [partsSignature, shouldReduceMotion, transition, width]);
|
|
99
|
+
return (_jsx(SectionContext.Provider, { value: context, children: _jsxs(m.span, { layoutDependency: layoutDependency, ...rest, className: `number-section-${name}`, ref: ref, style: {
|
|
82
100
|
...style,
|
|
83
101
|
display: "inline-flex",
|
|
84
102
|
justifyContent: justify,
|
|
103
|
+
// The containing block for glyphs AnimatePresence pops out of flow.
|
|
104
|
+
position: "relative",
|
|
85
105
|
width,
|
|
86
|
-
}, children:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
position: "relative",
|
|
90
|
-
}, children: ["\u200B", _jsx(AnimatePresence, { anchorX: justify, initial: false, mode: mode ?? "sync", children: parts.map((part) => isDigitPart(part) ? (_jsx(NumberDigit, { animate: { opacity: 1 }, exit: { opacity: 0 }, initial: { opacity: 0 }, layoutDependency: layoutDependency, trend: trend, value: part.value }, part.key)) : (_jsx(NumberSymbol, { animate: { opacity: 1 }, exit: { opacity: 0 }, initial: { opacity: 0 }, layoutDependency: layoutDependency, partKey: part.key, type: part.type, children: part.value }, part.type === "literal"
|
|
91
|
-
? `${part.key}:${part.value}`
|
|
92
|
-
: part.key))) })] }) }) }));
|
|
106
|
+
}, children: ["\u200B", _jsx(AnimatePresence, { anchorX: justify, initial: false, mode: mode ?? "sync", children: parts.map((part) => isDigitPart(part) ? (_jsx(NumberDigit, { animate: { opacity: 1 }, exit: { opacity: 0 }, initial: { opacity: 0 }, layoutDependency: layoutDependency, trend: trend, value: part.value }, part.key)) : (_jsx(NumberSymbol, { animate: { opacity: 1 }, exit: { opacity: 0 }, initial: { opacity: 0 }, layoutDependency: layoutDependency, partKey: part.key, type: part.type, children: part.value }, part.type === "literal"
|
|
107
|
+
? `${part.key}:${part.value}`
|
|
108
|
+
: part.key))) })] }) }));
|
|
93
109
|
};
|
|
@@ -16,6 +16,8 @@ export const NumberSymbol = ({ partKey: _partKey, type: _type, children, layoutD
|
|
|
16
16
|
const { justify } = use(SectionContext);
|
|
17
17
|
return (_jsx(m.span, { ...rest, "data-state": isPresent ? undefined : "exiting", layout: "position", layoutDependency: layoutDependency, style: {
|
|
18
18
|
display: "inline-flex",
|
|
19
|
+
// Holds its width while the section's width tweens past it.
|
|
20
|
+
flexShrink: 0,
|
|
19
21
|
justifyContent: justify,
|
|
20
22
|
padding: `calc(${maskHeight}/2) 0`,
|
|
21
23
|
position: "relative",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Transition } from "motion/react";
|
|
1
2
|
/**
|
|
2
3
|
* Per-digit target widths, written by {@link NumberDigit} the instant a digit's
|
|
3
4
|
* width changes and read by {@link NumberSection} when it re-measures its fixed
|
|
@@ -5,3 +6,9 @@
|
|
|
5
6
|
* element.
|
|
6
7
|
*/
|
|
7
8
|
export declare const targetWidths: WeakMap<Element, string>;
|
|
9
|
+
/**
|
|
10
|
+
* The timing for a tweened width: the layout curve when the transition is
|
|
11
|
+
* keyed per value (the token default), otherwise the transition itself (a
|
|
12
|
+
* caller's bespoke override), so widths move in step with the roll.
|
|
13
|
+
*/
|
|
14
|
+
export declare const widthTiming: (transition: Transition | undefined) => Transition;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// Port of Motion+ AnimateNumber (motion-plus 2.0.0).
|
|
2
1
|
/**
|
|
3
2
|
* Per-digit target widths, written by {@link NumberDigit} the instant a digit's
|
|
4
3
|
* width changes and read by {@link NumberSection} when it re-measures its fixed
|
|
@@ -6,3 +5,16 @@
|
|
|
6
5
|
* element.
|
|
7
6
|
*/
|
|
8
7
|
export const targetWidths = new WeakMap();
|
|
8
|
+
/**
|
|
9
|
+
* The timing for a tweened width: the layout curve when the transition is
|
|
10
|
+
* keyed per value (the token default), otherwise the transition itself (a
|
|
11
|
+
* caller's bespoke override), so widths move in step with the roll.
|
|
12
|
+
*/
|
|
13
|
+
export const widthTiming = (transition) => {
|
|
14
|
+
if (transition !== undefined &&
|
|
15
|
+
"layout" in transition &&
|
|
16
|
+
transition.layout !== undefined) {
|
|
17
|
+
return transition.layout;
|
|
18
|
+
}
|
|
19
|
+
return transition ?? {};
|
|
20
|
+
};
|
|
@@ -34,7 +34,8 @@ declare const getBaseSize: (size: ButtonSize) => ComponentSize;
|
|
|
34
34
|
* - neutral + outline → the bordered button (old `outline` variant)
|
|
35
35
|
* - neutral + ghost → the transparent button (old `ghost` variant)
|
|
36
36
|
* - neutral + link → the inline link button (old `link` variant)
|
|
37
|
-
* - neutral + transparent → the blurred translucent button
|
|
37
|
+
* - neutral + transparent → the blurred translucent button, for content
|
|
38
|
+
* over images only (see `tokens/emphasis`)
|
|
38
39
|
*/
|
|
39
40
|
declare const buttonVariants: (props?: ({
|
|
40
41
|
tone?: "brand" | "danger" | "neutral" | null | undefined;
|
|
@@ -156,7 +156,8 @@ const BRAND_TRANSPARENT = "transform-gpu border-transparent bg-brand/50 backdrop
|
|
|
156
156
|
* - neutral + outline → the bordered button (old `outline` variant)
|
|
157
157
|
* - neutral + ghost → the transparent button (old `ghost` variant)
|
|
158
158
|
* - neutral + link → the inline link button (old `link` variant)
|
|
159
|
-
* - neutral + transparent → the blurred translucent button
|
|
159
|
+
* - neutral + transparent → the blurred translucent button, for content
|
|
160
|
+
* over images only (see `tokens/emphasis`)
|
|
160
161
|
*/
|
|
161
162
|
const buttonVariants = cva([
|
|
162
163
|
"duration-quick text-paragraph-sm isolate inline-flex shrink-0 cursor-pointer items-center gap-2 border font-medium whitespace-nowrap transition ease-out",
|
|
@@ -22,6 +22,9 @@ export declare const DEFAULT_PILL_SIZE: ComponentSize;
|
|
|
22
22
|
* - `neutral` + `solid` → the dark primary pill (old default/solid)
|
|
23
23
|
* - `neutral` + `soft` → the grey secondary pill (old secondary/solid)
|
|
24
24
|
* - `neutral` + `outline` → the bordered transparent pill (old `outline` variant)
|
|
25
|
+
*
|
|
26
|
+
* `neutral` + `transparent` is glass for content over images only (see
|
|
27
|
+
* `tokens/emphasis`).
|
|
25
28
|
*/
|
|
26
29
|
export declare const pillVariants: (props?: ({
|
|
27
30
|
tone?: "brand" | "danger" | "info" | "neutral" | "success" | "warning" | null | undefined;
|
|
@@ -30,8 +30,15 @@ export const DEFAULT_PILL_SIZE = "base";
|
|
|
30
30
|
* - `neutral` + `solid` → the dark primary pill (old default/solid)
|
|
31
31
|
* - `neutral` + `soft` → the grey secondary pill (old secondary/solid)
|
|
32
32
|
* - `neutral` + `outline` → the bordered transparent pill (old `outline` variant)
|
|
33
|
+
*
|
|
34
|
+
* `neutral` + `transparent` is glass for content over images only (see
|
|
35
|
+
* `tokens/emphasis`).
|
|
33
36
|
*/
|
|
34
|
-
export const pillVariants = cva(cn("inline-flex items-center justify-center border border-transparent font-normal whitespace-nowrap [&_svg]:-ms-px [&_svg]:shrink-0", focusRing(),
|
|
37
|
+
export const pillVariants = cva(cn("inline-flex items-center justify-center border border-transparent font-normal whitespace-nowrap [&_svg]:-ms-px [&_svg]:shrink-0", focusRing(),
|
|
38
|
+
// Hover tints the fill with a 4% foreground wash laid over it as an image,
|
|
39
|
+
// so the text keeps its colour. 4% is the most every tone and emphasis takes
|
|
40
|
+
// and still reaches AA (styles/tone-contrast.test.ts, PILL_HOVER_WASH).
|
|
41
|
+
"data-[hovered=true]:from-foreground/4 data-[hovered=true]:to-foreground/4 data-[hovered=true]:bg-linear-to-b"), {
|
|
35
42
|
compoundVariants: [
|
|
36
43
|
/* ── Focus ring colours by tone ────────────────────────── */
|
|
37
44
|
{
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { springs } from "@instruments/materia-ui/tokens/motion";
|
|
4
4
|
import { cn } from "@instruments/materia-ui/utils/cn";
|
|
5
5
|
import * as m from "motion/react-m";
|
|
6
|
-
import { SEGMENTED_INDICATOR_CLASS } from "
|
|
6
|
+
import { SEGMENTED_INDICATOR_CLASS } from "../../tokens/segmented.js";
|
|
7
7
|
import { useSegmentedControlContext } from "./segmented-control-context.js";
|
|
8
8
|
/**
|
|
9
9
|
* Animated active-segment indicator. A single instance is mounted under the
|
|
@@ -5,9 +5,9 @@ import { Dot } from "@instruments/materia-ui/components/dot";
|
|
|
5
5
|
import { Icon } from "@instruments/materia-ui/components/icon";
|
|
6
6
|
import { cn } from "@instruments/materia-ui/utils/cn";
|
|
7
7
|
import { getResponsiveSizeOverrides } from "../../tokens/responsive.js";
|
|
8
|
+
import { SEGMENTED_TRIGGER_PADDING } from "../../tokens/segmented.js";
|
|
8
9
|
import { warnMissingAriaLabel } from "../../utils/warn-missing-aria-label.js";
|
|
9
10
|
import { Flex } from "../flex.js";
|
|
10
|
-
import { SEGMENTED_TRIGGER_PADDING } from "../segmented/segmented-root.js";
|
|
11
11
|
import { useSegmentedControlContext } from "./segmented-control-context.js";
|
|
12
12
|
import { SegmentedControlIndicator } from "./segmented-control-indicator.js";
|
|
13
13
|
import { segmentedControlItemVariants } from "./segmented-control-variants.js";
|
|
@@ -6,8 +6,8 @@ import { ScrollFade } from "@instruments/materia-ui/components/scroll-fade";
|
|
|
6
6
|
import { cn } from "@instruments/materia-ui/utils/cn";
|
|
7
7
|
import { useEffect, useId, useMemo, useRef, useState } from "react";
|
|
8
8
|
import { getResponsiveSizeOverrides, resolveResponsiveBase, } from "../../tokens/responsive.js";
|
|
9
|
+
import { SEGMENTED_GAP, SEGMENTED_SHELL_BASE, SEGMENTED_SHELL_PADDING, } from "../../tokens/segmented.js";
|
|
9
10
|
import { Flex } from "../flex.js";
|
|
10
|
-
import { SEGMENTED_GAP, SEGMENTED_SHELL_BASE, SEGMENTED_SHELL_PADDING, } from "../segmented/segmented-root.js";
|
|
11
11
|
import { SegmentedControlContext } from "./segmented-control-context.js";
|
|
12
12
|
import { useSingleSelection } from "./use-single-selection.js";
|
|
13
13
|
/**
|
|
@@ -2,8 +2,8 @@ import { focusRing } from "@instruments/materia-ui/tokens/focus-ring";
|
|
|
2
2
|
import { cn } from "@instruments/materia-ui/utils/cn";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
4
|
import { DISABLED } from "../../tokens/input-shell.js";
|
|
5
|
+
import { SEGMENTED_TRIGGER_PADDING, SEGMENTED_TRIGGER_TEXT, } from "../../tokens/segmented.js";
|
|
5
6
|
import { typedEntries } from "../../utils/typed-entries.js";
|
|
6
|
-
import { SEGMENTED_TRIGGER_PADDING, SEGMENTED_TRIGGER_TEXT, } from "../segmented/segmented-root.js";
|
|
7
7
|
/**
|
|
8
8
|
* Item padding lives in compound variants (mirroring `tabsTriggerVariants`) so a
|
|
9
9
|
* single size dimension drives text (base `size` variant) and padding
|
|
@@ -58,7 +58,11 @@ export const StepperItem = ({ value, label, disabled = false, icon = Check, clas
|
|
|
58
58
|
const circleSize = state === "completed"
|
|
59
59
|
? CIRCLE_SIZES[baseSize].completed
|
|
60
60
|
: CIRCLE_SIZES[baseSize].default;
|
|
61
|
-
return (_jsxs("div", { className: cn("relative", className), "data-slot": "stepper-item", "data-state": state, ...rest, children: [_jsx("div", { className: cn(stepperCircleContainerVariants({ size: baseSize }), getResponsiveSizeOverrides(size, STEPPER_CIRCLE_CONTAINER_SIZE_CLASSES)), children: _jsx(m.button, { "aria-current": state === "current" ? "step" : undefined, "aria-label": label !== undefined && label !== null && label !== ""
|
|
61
|
+
return (_jsxs("div", { className: cn("group/step relative", className), "data-slot": "stepper-item", "data-state": state, ...rest, children: [_jsx("div", { className: cn(stepperCircleContainerVariants({ size: baseSize }), getResponsiveSizeOverrides(size, STEPPER_CIRCLE_CONTAINER_SIZE_CLASSES)), children: _jsx(m.button, { "aria-current": state === "current" ? "step" : undefined, "aria-label": label !== undefined && label !== null && label !== ""
|
|
62
62
|
? `Step: ${label}`
|
|
63
|
-
: `Step ${value + 1}`, className: cn(stepperCircleVariants({ size: baseSize, state }), isInteractive && "cursor-pointer", !isInteractive && "cursor-default"), disabled: disabled, layout: true, onClick: handleClick, onKeyDown: handleKeyDown, style: { borderRadius: 9999, height: circleSize, width: circleSize }, transition: springs.standard, type: "button", children: _jsx(m.span, { className: "flex", layout: true, children: _jsx(Icon, { className: stepperIconVariants({ size: baseSize, state }), icon: icon, size: iconSize }) }) }) }), label !== undefined && label !== null && label !== "" && (_jsx("span", { className: cn(stepperLabelVariants({
|
|
63
|
+
: `Step ${value + 1}`, className: cn(stepperCircleVariants({ size: baseSize, state }), isInteractive && "cursor-pointer", !isInteractive && "cursor-default"), disabled: disabled, layout: true, onClick: handleClick, onKeyDown: handleKeyDown, style: { borderRadius: 9999, height: circleSize, width: circleSize }, transition: springs.standard, type: "button", children: _jsx(m.span, { className: "flex", layout: true, children: _jsx(Icon, { className: stepperIconVariants({ size: baseSize, state }), icon: icon, size: iconSize }) }) }) }), label !== undefined && label !== null && label !== "" && (_jsx("span", { className: cn(stepperLabelVariants({
|
|
64
|
+
edge: value === 0 ? "start" : "center",
|
|
65
|
+
size: baseSize,
|
|
66
|
+
state,
|
|
67
|
+
}), getResponsiveSizeOverrides(size, STEPPER_LABEL_SIZE_CLASSES)), children: label }))] }));
|
|
64
68
|
};
|
|
@@ -36,6 +36,7 @@ export declare const stepperIconVariants: (props?: ({
|
|
|
36
36
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
37
37
|
/** Step label variants - absolutely positioned to not affect flex layout */
|
|
38
38
|
export declare const stepperLabelVariants: (props?: ({
|
|
39
|
+
edge?: "center" | "start" | null | undefined;
|
|
39
40
|
size?: StepperSize | null | undefined;
|
|
40
41
|
state?: "completed" | "current" | "disabled" | "upcoming" | null | undefined;
|
|
41
42
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
@@ -105,12 +105,22 @@ export const stepperIconVariants = cva("transition-colors", {
|
|
|
105
105
|
},
|
|
106
106
|
});
|
|
107
107
|
/** Step label variants - absolutely positioned to not affect flex layout */
|
|
108
|
-
|
|
108
|
+
// Labels centre under their circles, except at the ends: the first starts at
|
|
109
|
+
// its circle's left edge and the last ends at its circle's right edge, so no
|
|
110
|
+
// label hangs outside the stepper's box. The last step is found with CSS
|
|
111
|
+
// (`last-child` of the root, via the item's `group/step`) so it holds on the
|
|
112
|
+
// server render; the first is passed in as `edge`.
|
|
113
|
+
export const stepperLabelVariants = cva("text-caption absolute whitespace-nowrap group-last/step:right-0 group-last/step:left-auto group-last/step:translate-x-0 group-last/step:text-right", {
|
|
109
114
|
defaultVariants: {
|
|
115
|
+
edge: "center",
|
|
110
116
|
size: "base",
|
|
111
117
|
state: "upcoming",
|
|
112
118
|
},
|
|
113
119
|
variants: {
|
|
120
|
+
edge: {
|
|
121
|
+
center: "left-1/2 -translate-x-1/2 text-center",
|
|
122
|
+
start: "left-0 text-left",
|
|
123
|
+
},
|
|
114
124
|
size: STEPPER_LABEL_SIZE_CLASSES,
|
|
115
125
|
state: {
|
|
116
126
|
completed: "text-foreground",
|
|
@@ -4,9 +4,9 @@ import { mergeProps } from "@base-ui/react/merge-props";
|
|
|
4
4
|
import { useRender } from "@base-ui/react/use-render";
|
|
5
5
|
import { cn } from "@instruments/materia-ui/utils/cn";
|
|
6
6
|
import { getResponsiveSizeOverrides, resolveResponsiveBase, } from "../../tokens/responsive.js";
|
|
7
|
+
import { SEGMENTED_ICON_SIZE, SEGMENTED_TRIGGER_TEXT, } from "../../tokens/segmented.js";
|
|
7
8
|
import { Flex } from "../flex.js";
|
|
8
9
|
import { Icon } from "../icon.js";
|
|
9
|
-
import { SEGMENTED_ICON_SIZE, SEGMENTED_TRIGGER_TEXT, } from "../segmented/segmented-variants.js";
|
|
10
10
|
import { useTabsContext } from "../tabs/tabs-context.js";
|
|
11
11
|
import { TabsIndicator } from "../tabs/tabs-indicator.js";
|
|
12
12
|
import { DEFAULT_TABS_SIZE, tabsTriggerVariants } from "../tabs/tabs-variants.js";
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { springs } from "@instruments/materia-ui/tokens/motion";
|
|
4
4
|
import { cn } from "@instruments/materia-ui/utils/cn";
|
|
5
5
|
import * as m from "motion/react-m";
|
|
6
|
-
import { SEGMENTED_INDICATOR_CLASS } from "
|
|
6
|
+
import { SEGMENTED_INDICATOR_CLASS } from "../../tokens/segmented.js";
|
|
7
7
|
import { useTabsContext } from "../tabs/tabs-context.js";
|
|
8
8
|
const indicatorStyles = {
|
|
9
9
|
line: "absolute inset-x-0 bottom-0 h-0.5 bg-primary dark:bg-gray-100",
|
|
@@ -3,10 +3,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
|
|
4
4
|
import { cn } from "@instruments/materia-ui/utils/cn";
|
|
5
5
|
import { getResponsiveSizeOverrides, resolveResponsiveBase, } from "../../tokens/responsive.js";
|
|
6
|
+
import { SEGMENTED_DOT_SIZE, SEGMENTED_ICON_SIZE, SEGMENTED_TRIGGER_TEXT, } from "../../tokens/segmented.js";
|
|
6
7
|
import { Dot } from "../dot.js";
|
|
7
8
|
import { Flex } from "../flex.js";
|
|
8
9
|
import { Icon } from "../icon.js";
|
|
9
|
-
import { SEGMENTED_DOT_SIZE, SEGMENTED_ICON_SIZE, SEGMENTED_TRIGGER_TEXT, } from "../segmented/segmented-variants.js";
|
|
10
10
|
import { useTabsContext } from "../tabs/tabs-context.js";
|
|
11
11
|
import { TabsIndicator } from "../tabs/tabs-indicator.js";
|
|
12
12
|
import { DEFAULT_TABS_SIZE, tabsTriggerVariants } from "../tabs/tabs-variants.js";
|
|
@@ -2,8 +2,8 @@ import { focusRing } from "@instruments/materia-ui/tokens/focus-ring";
|
|
|
2
2
|
import { cn } from "@instruments/materia-ui/utils/cn";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
4
|
import { DISABLED } from "../../tokens/input-shell.js";
|
|
5
|
+
import { SEGMENTED_GAP, SEGMENTED_SHELL_BASE, SEGMENTED_SHELL_PADDING, SEGMENTED_TRIGGER_PADDING, SEGMENTED_TRIGGER_TEXT, } from "../../tokens/segmented.js";
|
|
5
6
|
import { typedEntries } from "../../utils/typed-entries.js";
|
|
6
|
-
import { SEGMENTED_GAP, SEGMENTED_SHELL_BASE, SEGMENTED_SHELL_PADDING, SEGMENTED_TRIGGER_PADDING, SEGMENTED_TRIGGER_TEXT, } from "../segmented/segmented-root.js";
|
|
7
7
|
const segmentedShellPaddingVariants = typedEntries(SEGMENTED_SHELL_PADDING).map(([size, className]) => ({
|
|
8
8
|
className,
|
|
9
9
|
size,
|
|
@@ -72,5 +72,5 @@ export interface DocsManifest {
|
|
|
72
72
|
storybook: DocsStorybookInventory;
|
|
73
73
|
}
|
|
74
74
|
/** The slug of every documented component and composition. */
|
|
75
|
-
export type DocsUnitSlug = "accordion" | "alert" | "alert-dialog" | "anchor" | "animated-number" | "aspect-ratio" | "attribute-card" | "avatar" | "badge" | "border-beam" | "box" | "breadcrumb" | "button" | "button-group" | "calendar" | "card" | "card-grid" | "carousel" | "center" | "chart" | "checkbox" | "checkbox-card" | "checkbox-field" | "code" | "collapsible" | "color-palette" | "color-wheel" | "combobox" | "command" | "container" | "context-menu" | "cookie-banner" | "country-combobox" | "country-select" | "data-grid" | "data-grid-column-filter" | "data-grid-column-header" | "data-grid-column-visibility" | "data-grid-pagination" | "data-grid-table" | "delivery-banner" | "description-list" | "dialog" | "document-list" | "dot" | "dot-matrix-loader" | "drawer" | "dropdown-menu" | "easing-curve" | "editorial-card" | "empty" | "empty-state" | "error-boundary" | "error-state" | "expand-button" | "expandable-text" | "fade-overlay" | "field" | "file-upload" | "flex" | "folder-tile" | "gallery" | "grid" | "group" | "heading" | "hotspot-indicator" | "hover-card" | "icon" | "icon-box" | "icon-card" | "icon-morph" | "identified-surface" | "input" | "input-card" | "input-group" | "input-otp" | "item" | "kbd" | "label" | "loading-state" | "location-input" | "logo" | "marquee-text" | "media-frame" | "mega-menu" | "menu-item" | "menubar" | "mobile-navigation" | "month-year-picker" | "morph-dialog" | "motion-config" | "native-select" | "navigation-menu" | "page-header" | "page-toolbar" | "pagination" | "phone-input" | "pill" | "pixel-loader" | "popover" | "product-card" | "product-specifications-selector" | "progress" | "progress-dots" | "prompt-panel" | "quantity-control" | "quick-view" | "radio" | "radio-card" | "radio-group" | "requirement-item" | "resizable" | "scrim" | "scroll-area" | "scroll-fade" | "scroll-frame" | "search-input" | "section" | "section-header" | "segmented
|
|
75
|
+
export type DocsUnitSlug = "accordion" | "alert" | "alert-dialog" | "anchor" | "animated-number" | "aspect-ratio" | "attribute-card" | "avatar" | "badge" | "border-beam" | "box" | "breadcrumb" | "button" | "button-group" | "calendar" | "card" | "card-grid" | "carousel" | "center" | "chart" | "checkbox" | "checkbox-card" | "checkbox-field" | "code" | "collapsible" | "color-palette" | "color-wheel" | "combobox" | "command" | "container" | "context-menu" | "cookie-banner" | "country-combobox" | "country-select" | "data-grid" | "data-grid-column-filter" | "data-grid-column-header" | "data-grid-column-visibility" | "data-grid-pagination" | "data-grid-table" | "delivery-banner" | "description-list" | "dialog" | "document-list" | "dot" | "dot-matrix-loader" | "drawer" | "dropdown-menu" | "easing-curve" | "editorial-card" | "empty" | "empty-state" | "error-boundary" | "error-state" | "expand-button" | "expandable-text" | "fade-overlay" | "field" | "file-upload" | "flex" | "folder-tile" | "gallery" | "grid" | "group" | "heading" | "hotspot-indicator" | "hover-card" | "icon" | "icon-box" | "icon-card" | "icon-morph" | "identified-surface" | "input" | "input-card" | "input-group" | "input-otp" | "item" | "kbd" | "label" | "loading-state" | "location-input" | "logo" | "marquee-text" | "media-frame" | "mega-menu" | "menu-item" | "menubar" | "mobile-navigation" | "month-year-picker" | "morph-dialog" | "motion-config" | "native-select" | "navigation-menu" | "page-header" | "page-toolbar" | "pagination" | "phone-input" | "pill" | "pixel-loader" | "popover" | "product-card" | "product-specifications-selector" | "progress" | "progress-dots" | "prompt-panel" | "quantity-control" | "quick-view" | "radio" | "radio-card" | "radio-group" | "requirement-item" | "resizable" | "scrim" | "scroll-area" | "scroll-fade" | "scroll-frame" | "search-input" | "section" | "section-header" | "segmented-control" | "select" | "separator" | "settings-card" | "sheet" | "shimmer" | "shine" | "sidebar" | "sidepanel" | "simple-grid" | "site-footer" | "skeleton" | "slider" | "social-icon" | "space" | "spinner" | "stack" | "stagger" | "stepper" | "streaming-text" | "sub-navigation" | "success-tick-ripple" | "swatch" | "swatch-fan" | "swatch-group" | "swatch-selector" | "switch" | "tab-navigation" | "table" | "tabs" | "tag" | "tag-selector" | "text" | "textarea" | "thinking-loader" | "thinking-orb" | "thinking-trace" | "time-slot-picker" | "toast" | "toggle" | "toggle-group" | "tooltip" | "variant-selector" | "verge" | "visually-hidden" | "voice";
|
|
76
76
|
export declare const docsManifest: DocsManifest;
|
|
@@ -9393,51 +9393,6 @@ export const docsManifest = {
|
|
|
9393
9393
|
uses: [],
|
|
9394
9394
|
usedBy: [],
|
|
9395
9395
|
},
|
|
9396
|
-
{
|
|
9397
|
-
slug: "segmented",
|
|
9398
|
-
module: "components/segmented",
|
|
9399
|
-
title: "Segmented",
|
|
9400
|
-
category: "forms",
|
|
9401
|
-
description: "Shared class-name token primitives for the segmented-control visual language.\n\nConsumed by SegmentedControl, Tabs, and TabNavigation so they render an\nidentical shell, padding, gap, and active indicator at any size.\nNot components - these are Tailwind class strings and size→class maps.",
|
|
9402
|
-
exports: [
|
|
9403
|
-
{
|
|
9404
|
-
name: "SEGMENTED_GAP",
|
|
9405
|
-
kind: "const",
|
|
9406
|
-
props: [],
|
|
9407
|
-
},
|
|
9408
|
-
{
|
|
9409
|
-
name: "SEGMENTED_INDICATOR_ACTIVE_CLASS",
|
|
9410
|
-
kind: "const",
|
|
9411
|
-
props: [],
|
|
9412
|
-
},
|
|
9413
|
-
{
|
|
9414
|
-
name: "SEGMENTED_INDICATOR_CLASS",
|
|
9415
|
-
kind: "const",
|
|
9416
|
-
props: [],
|
|
9417
|
-
},
|
|
9418
|
-
{
|
|
9419
|
-
name: "SEGMENTED_SHELL_BASE",
|
|
9420
|
-
kind: "const",
|
|
9421
|
-
props: [],
|
|
9422
|
-
},
|
|
9423
|
-
{
|
|
9424
|
-
name: "SEGMENTED_SHELL_PADDING",
|
|
9425
|
-
kind: "const",
|
|
9426
|
-
props: [],
|
|
9427
|
-
},
|
|
9428
|
-
{
|
|
9429
|
-
name: "SEGMENTED_TRIGGER_PADDING",
|
|
9430
|
-
kind: "const",
|
|
9431
|
-
props: [],
|
|
9432
|
-
},
|
|
9433
|
-
],
|
|
9434
|
-
bestPractices: [
|
|
9435
|
-
"These are class-name strings and size→class maps, not components - apply them via `className`, never render them as JSX.",
|
|
9436
|
-
'Reuse these tokens to give a custom control the segmented "pill" shell; for actual segmented UIs prefer `SegmentedControl`; `Tabs` and `TabNavigation` consume the same tokens for panel and navigation switching.',
|
|
9437
|
-
],
|
|
9438
|
-
uses: [],
|
|
9439
|
-
usedBy: ["segmented-control", "tab-navigation", "tabs"],
|
|
9440
|
-
},
|
|
9441
9396
|
{
|
|
9442
9397
|
slug: "segmented-control",
|
|
9443
9398
|
module: "components/segmented-control",
|
|
@@ -9542,7 +9497,7 @@ export const docsManifest = {
|
|
|
9542
9497
|
"Switching visible content panels is Tabs; navigating URLs is TabNavigation - SegmentedControl is an input that selects a value or mode.",
|
|
9543
9498
|
"Icon-only items must carry an `aria-label`.",
|
|
9544
9499
|
],
|
|
9545
|
-
uses: ["dot", "flex", "icon", "scroll-area", "scroll-fade"
|
|
9500
|
+
uses: ["dot", "flex", "icon", "scroll-area", "scroll-fade"],
|
|
9546
9501
|
usedBy: ["color-palette"],
|
|
9547
9502
|
anatomy: {
|
|
9548
9503
|
children: [
|
|
@@ -11941,7 +11896,7 @@ export const docsManifest = {
|
|
|
11941
11896
|
"Use `TabNavigation` when each tab is a route/URL (it renders a `<nav>` of links); for in-page panels that swap without navigating, use `Tabs`.",
|
|
11942
11897
|
'It shares the Tabs look via context but defaults to `variant="line"`; mark the active link yourself (e.g. router `aria-current`) - there\'s no Base UI value tracking here.',
|
|
11943
11898
|
],
|
|
11944
|
-
uses: ["flex", "icon", "
|
|
11899
|
+
uses: ["flex", "icon", "tabs"],
|
|
11945
11900
|
usedBy: [],
|
|
11946
11901
|
anatomy: {
|
|
11947
11902
|
children: [
|
|
@@ -12183,7 +12138,7 @@ export const docsManifest = {
|
|
|
12183
12138
|
"Reach for `Tabs` to swap in-page panels via `TabsContent`; when each tab is a route, use `TabNavigation` instead (renders links, no panels).",
|
|
12184
12139
|
"Compose `TabsList`/`TabsTrigger`/`TabsContent` - shared `variant`/`size`/`fullWidth` flow through context, so set them on `Tabs`, not each trigger.",
|
|
12185
12140
|
],
|
|
12186
|
-
uses: ["dot", "flex", "icon", "scroll-area"
|
|
12141
|
+
uses: ["dot", "flex", "icon", "scroll-area"],
|
|
12187
12142
|
usedBy: ["tab-navigation"],
|
|
12188
12143
|
anatomy: {
|
|
12189
12144
|
children: [
|
|
@@ -23055,7 +23010,7 @@ export const docsManifest = {
|
|
|
23055
23010
|
storybook: {
|
|
23056
23011
|
addons: ["@storybook/addon-docs", "@storybook/react-vite"],
|
|
23057
23012
|
anatomyFiles: 31,
|
|
23058
|
-
storyFiles:
|
|
23059
|
-
storiesWithPlay:
|
|
23013
|
+
storyFiles: 171,
|
|
23014
|
+
storiesWithPlay: 63,
|
|
23060
23015
|
},
|
|
23061
23016
|
};
|
package/dist/styles/index.css
CHANGED
|
@@ -233,7 +233,13 @@
|
|
|
233
233
|
--color-info-foreground: #1e3a5f; /* blue-900 */
|
|
234
234
|
|
|
235
235
|
--color-brand: #ffe600; /* Brand yellow, per the Figma base/brand token */
|
|
236
|
-
|
|
236
|
+
/* Pale brand yellow (code-only), mixed from --color-brand so a re-brand
|
|
237
|
+
carries its soft fill with it: #fffbd9 for the default yellow. */
|
|
238
|
+
--color-brand-soft: color-mix(
|
|
239
|
+
in srgb,
|
|
240
|
+
var(--color-brand) 15%,
|
|
241
|
+
var(--color-white)
|
|
242
|
+
);
|
|
237
243
|
--color-brand-accent: #dda600; /* per Figma base/brand accent - consumed at /40 for borders/accents */
|
|
238
244
|
--color-brand-foreground: var(--color-gray-800); /* #1F2937 - per Figma */
|
|
239
245
|
/* Brand-hued text on the page background, for brand buttons without a fill.
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
* cva-backed components anchor their variant map `satisfies Record<Emphasis, …>`
|
|
11
11
|
* so the fill vocabulary stays in lockstep with the canon.
|
|
12
12
|
*
|
|
13
|
+
* `transparent` is a blurred half fill of the tone. Its brand and status tones
|
|
14
|
+
* are tuned to read on the page. Neutral transparent is glass for
|
|
15
|
+
* content over images: its white text reaches AA over a mid-to-dark photo or a
|
|
16
|
+
* Scrim, but not on the page or a pale image, so keep it to media.
|
|
17
|
+
*
|
|
13
18
|
* `link` is deliberately NOT a member: it is a Button-only extension expressed
|
|
14
19
|
* as `ButtonEmphasis = Emphasis | "link"`, so no other component inherits an
|
|
15
20
|
* inline-link treatment it has no styling for.
|
package/dist/tokens/emphasis.js
CHANGED
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
* cva-backed components anchor their variant map `satisfies Record<Emphasis, …>`
|
|
11
11
|
* so the fill vocabulary stays in lockstep with the canon.
|
|
12
12
|
*
|
|
13
|
+
* `transparent` is a blurred half fill of the tone. Its brand and status tones
|
|
14
|
+
* are tuned to read on the page. Neutral transparent is glass for
|
|
15
|
+
* content over images: its white text reaches AA over a mid-to-dark photo or a
|
|
16
|
+
* Scrim, but not on the page or a pale image, so keep it to media.
|
|
17
|
+
*
|
|
13
18
|
* `link` is deliberately NOT a member: it is a Button-only extension expressed
|
|
14
19
|
* as `ButtonEmphasis = Emphasis | "link"`, so no other component inherits an
|
|
15
20
|
* inline-link treatment it has no styling for.
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The segmented "pill" visual language: shell, padding, gap, trigger text and
|
|
3
|
+
* active-indicator class strings, with size-to-class maps.
|
|
4
|
+
*
|
|
5
|
+
* SegmentedControl, Tabs and TabNavigation all consume these, so they render
|
|
6
|
+
* an identical shell at any size. They are class strings, not components:
|
|
7
|
+
* apply them through `className`. For an actual segmented UI, use
|
|
8
|
+
* `SegmentedControl`.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
import type { ComponentSize } from "./size.js";
|
|
2
13
|
/**
|
|
3
14
|
* Shared shell classes for segmented controls (Tabs default, ToggleGroup pill).
|
|
4
15
|
*
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The segmented "pill" visual language: shell, padding, gap, trigger text and
|
|
3
|
+
* active-indicator class strings, with size-to-class maps.
|
|
4
|
+
*
|
|
5
|
+
* SegmentedControl, Tabs and TabNavigation all consume these, so they render
|
|
6
|
+
* an identical shell at any size. They are class strings, not components:
|
|
7
|
+
* apply them through `className`. For an actual segmented UI, use
|
|
8
|
+
* `SegmentedControl`.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
1
12
|
/**
|
|
2
13
|
* Shared shell classes for segmented controls (Tabs default, ToggleGroup pill).
|
|
3
14
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@instruments/materia-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Materia design system: React components, tokens and Tailwind v4 styles.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"repository": {
|
|
@@ -392,10 +392,6 @@
|
|
|
392
392
|
"types": "./dist/components/section.d.ts",
|
|
393
393
|
"default": "./dist/components/section.js"
|
|
394
394
|
},
|
|
395
|
-
"./components/segmented": {
|
|
396
|
-
"types": "./dist/components/segmented.d.ts",
|
|
397
|
-
"default": "./dist/components/segmented.js"
|
|
398
|
-
},
|
|
399
395
|
"./components/segmented-control": {
|
|
400
396
|
"types": "./dist/components/segmented-control.d.ts",
|
|
401
397
|
"default": "./dist/components/segmented-control.js"
|
|
@@ -792,6 +788,10 @@
|
|
|
792
788
|
"types": "./dist/tokens/radius-tokens.d.ts",
|
|
793
789
|
"default": "./dist/tokens/radius-tokens.js"
|
|
794
790
|
},
|
|
791
|
+
"./tokens/segmented": {
|
|
792
|
+
"types": "./dist/tokens/segmented.d.ts",
|
|
793
|
+
"default": "./dist/tokens/segmented.js"
|
|
794
|
+
},
|
|
795
795
|
"./tokens/shadows": {
|
|
796
796
|
"types": "./dist/tokens/shadows.d.ts",
|
|
797
797
|
"default": "./dist/tokens/shadows.js"
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared class-name token primitives for the segmented-control visual language.
|
|
3
|
-
*
|
|
4
|
-
* Consumed by SegmentedControl, Tabs, and TabNavigation so they render an
|
|
5
|
-
* identical shell, padding, gap, and active indicator at any size.
|
|
6
|
-
* Not components - these are Tailwind class strings and size→class maps.
|
|
7
|
-
*
|
|
8
|
-
* @bestPractice These are class-name strings and size→class maps, not components - apply them via `className`, never render them as JSX.
|
|
9
|
-
* @bestPractice Reuse these tokens to give a custom control the segmented "pill" shell; for actual segmented UIs prefer `SegmentedControl`; `Tabs` and `TabNavigation` consume the same tokens for panel and navigation switching.
|
|
10
|
-
* @category forms
|
|
11
|
-
*/
|
|
12
|
-
export { SEGMENTED_GAP, SEGMENTED_INDICATOR_ACTIVE_CLASS, SEGMENTED_INDICATOR_CLASS, SEGMENTED_SHELL_BASE, SEGMENTED_SHELL_PADDING, SEGMENTED_TRIGGER_PADDING, SEGMENTED_TRIGGER_TEXT, } from "./segmented-variants.js";
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared class-name token primitives for the segmented-control visual language.
|
|
3
|
-
*
|
|
4
|
-
* Consumed by SegmentedControl, Tabs, and TabNavigation so they render an
|
|
5
|
-
* identical shell, padding, gap, and active indicator at any size.
|
|
6
|
-
* Not components - these are Tailwind class strings and size→class maps.
|
|
7
|
-
*
|
|
8
|
-
* @bestPractice These are class-name strings and size→class maps, not components - apply them via `className`, never render them as JSX.
|
|
9
|
-
* @bestPractice Reuse these tokens to give a custom control the segmented "pill" shell; for actual segmented UIs prefer `SegmentedControl`; `Tabs` and `TabNavigation` consume the same tokens for panel and navigation switching.
|
|
10
|
-
* @category forms
|
|
11
|
-
*/
|
|
12
|
-
export { SEGMENTED_GAP, SEGMENTED_INDICATOR_ACTIVE_CLASS, SEGMENTED_INDICATOR_CLASS, SEGMENTED_SHELL_BASE, SEGMENTED_SHELL_PADDING, SEGMENTED_TRIGGER_PADDING, SEGMENTED_TRIGGER_TEXT, } from "./segmented-variants.js";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { SEGMENTED_GAP, SEGMENTED_INDICATOR_ACTIVE_CLASS, SEGMENTED_INDICATOR_CLASS, SEGMENTED_SHELL_BASE, SEGMENTED_SHELL_PADDING, SEGMENTED_TRIGGER_PADDING, } from "./segmented/segmented-variants.js";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { SEGMENTED_GAP, SEGMENTED_INDICATOR_ACTIVE_CLASS, SEGMENTED_INDICATOR_CLASS, SEGMENTED_SHELL_BASE, SEGMENTED_SHELL_PADDING, SEGMENTED_TRIGGER_PADDING, } from "./segmented/segmented-variants.js";
|