@instruments/materia-ui 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/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/{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
|
+
};
|
|
@@ -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
|
};
|
|
@@ -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.0",
|
|
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";
|