@inkra/react 0.8.0 → 0.10.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/README.md +137 -2
- package/THIRD_PARTY_NOTICES.md +9 -0
- package/Xiaolai-OFL.txt +94 -0
- package/dist/PencilIcon.d.ts +14 -1
- package/dist/PencilIcon.js +88 -52
- package/dist/PencilText.js +4 -1
- package/dist/assets/ui-hand-xiaolai-extended.woff2 +0 -0
- package/dist/assets/ui-hand-xiaolai.woff2 +0 -0
- package/dist/assets/xiaolai.css +17 -0
- package/dist/form/EditablePencilText.js +4 -2
- package/dist/guide/index.d.ts +77 -0
- package/dist/guide/index.js +379 -0
- package/dist/i18n/catalog.d.ts +1 -1
- package/dist/i18n/catalog.js +8 -0
- package/dist/image-viewer/index.d.ts +2 -0
- package/dist/image-viewer/index.js +28 -28
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/infinite-scroll/gesture.d.ts +19 -0
- package/dist/infinite-scroll/gesture.js +218 -0
- package/dist/infinite-scroll/index.d.ts +55 -0
- package/dist/infinite-scroll/index.js +543 -0
- package/dist/infinite-scroll/model.d.ts +33 -0
- package/dist/infinite-scroll/model.js +168 -0
- package/dist/internal/iconFill.d.ts +16 -0
- package/dist/internal/iconFill.js +275 -0
- package/dist/internal/surfaceRenderer.d.ts +4 -4
- package/dist/internal/surfaceRenderer.js +2 -2
- package/dist/internal/textRenderer.d.ts +1 -1
- package/dist/internal/textRenderer.js +23 -11
- package/dist/inventory-grid/index.js +13 -3
- package/dist/inventory-grid/outline.d.ts +9 -0
- package/dist/inventory-grid/outline.js +54 -0
- package/dist/loading/index.d.ts +58 -0
- package/dist/loading/index.js +188 -0
- package/dist/loading/locales.d.ts +15 -0
- package/dist/loading/locales.js +34 -0
- package/dist/loading/model.d.ts +6 -0
- package/dist/loading/model.js +29 -0
- package/dist/masonry/index.d.ts +25 -0
- package/dist/masonry/index.js +227 -0
- package/dist/masonry/layout.d.ts +35 -0
- package/dist/masonry/layout.js +67 -0
- package/dist/number-ticker/RollingValue.d.ts +6 -0
- package/dist/number-ticker/RollingValue.js +10 -0
- package/dist/number-ticker/index.d.ts +4 -0
- package/dist/number-ticker/index.js +55 -7
- package/dist/number-ticker/rolling.d.ts +35 -0
- package/dist/number-ticker/rolling.js +87 -0
- package/dist/selection/CircularSwitchVisual.d.ts +8 -0
- package/dist/selection/CircularSwitchVisual.js +82 -0
- package/dist/selection/Switch.d.ts +7 -0
- package/dist/selection/Switch.js +15 -3
- package/dist/slider/circular.d.ts +35 -0
- package/dist/slider/circular.js +196 -0
- package/dist/slider/circularMath.d.ts +10 -0
- package/dist/slider/circularMath.js +34 -0
- package/dist/slider/index.d.ts +5 -0
- package/dist/slider/index.js +5 -3
- package/dist/styles/base.css +4 -2
- package/dist/styles/breadcrumb.css +1 -1
- package/dist/styles/guide.css +202 -0
- package/dist/styles/image-viewer.css +20 -0
- package/dist/styles/infinite-scroll.css +171 -0
- package/dist/styles/inventory-grid.css +20 -8
- package/dist/styles/loading.css +348 -0
- package/dist/styles/markdown.css +1 -1
- package/dist/styles/masonry.css +68 -0
- package/dist/styles/number-ticker.css +47 -1
- package/dist/styles/selection.css +50 -0
- package/dist/styles/slider.css +65 -0
- package/dist/styles/slot.css +1 -1
- package/dist/styles.css +1180 -192
- package/dist/toast/manager.d.ts +4 -4
- package/dist/tokens.css +13 -10
- package/package.json +28 -2
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type MasonryStrategy = 'shortest' | 'sequential';
|
|
2
|
+
export type MasonryDirection = 'ltr' | 'rtl';
|
|
3
|
+
export interface MasonryLayoutItem {
|
|
4
|
+
key: string;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
export interface MasonryLayoutOptions {
|
|
8
|
+
containerWidth: number;
|
|
9
|
+
items: readonly MasonryLayoutItem[];
|
|
10
|
+
columns?: number;
|
|
11
|
+
minColumnWidth?: number;
|
|
12
|
+
maxColumns?: number;
|
|
13
|
+
columnGap?: number;
|
|
14
|
+
rowGap?: number;
|
|
15
|
+
strategy?: MasonryStrategy;
|
|
16
|
+
direction?: MasonryDirection;
|
|
17
|
+
}
|
|
18
|
+
export interface MasonryLayoutPosition {
|
|
19
|
+
key: string;
|
|
20
|
+
column: number;
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
}
|
|
26
|
+
export interface MasonryLayoutResult {
|
|
27
|
+
columnCount: number;
|
|
28
|
+
columnWidth: number;
|
|
29
|
+
height: number;
|
|
30
|
+
positions: readonly MasonryLayoutPosition[];
|
|
31
|
+
}
|
|
32
|
+
export declare function normalizeMasonrySpacing(value: number | undefined, fallback?: number): number;
|
|
33
|
+
export declare function normalizeMasonryColumnCount(value: number | undefined, fallback?: number): number;
|
|
34
|
+
export declare function resolveMasonryColumnCount({ columns, containerWidth, columnGap, maxColumns, minColumnWidth, }: Pick<MasonryLayoutOptions, 'columns' | 'containerWidth' | 'columnGap' | 'maxColumns' | 'minColumnWidth'>): number;
|
|
35
|
+
export declare function calculateMasonryLayout(options: MasonryLayoutOptions): MasonryLayoutResult;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const DEFAULT_MIN_COLUMN_WIDTH = 240;
|
|
2
|
+
const MAX_COLUMN_COUNT = 100;
|
|
3
|
+
export function normalizeMasonrySpacing(value, fallback = 0) {
|
|
4
|
+
return Number.isFinite(value) ? Math.max(0, value) : fallback;
|
|
5
|
+
}
|
|
6
|
+
export function normalizeMasonryColumnCount(value, fallback = 1) {
|
|
7
|
+
if (!Number.isFinite(value))
|
|
8
|
+
return fallback;
|
|
9
|
+
return Math.min(MAX_COLUMN_COUNT, Math.max(1, Math.floor(value)));
|
|
10
|
+
}
|
|
11
|
+
export function resolveMasonryColumnCount({ columns, containerWidth, columnGap = 0, maxColumns, minColumnWidth = DEFAULT_MIN_COLUMN_WIDTH, }) {
|
|
12
|
+
if (columns !== undefined)
|
|
13
|
+
return normalizeMasonryColumnCount(columns);
|
|
14
|
+
const width = Number.isFinite(containerWidth) ? Math.max(0, containerWidth) : 0;
|
|
15
|
+
const gap = normalizeMasonrySpacing(columnGap);
|
|
16
|
+
const minimum = Number.isFinite(minColumnWidth) && minColumnWidth > 0
|
|
17
|
+
? minColumnWidth
|
|
18
|
+
: DEFAULT_MIN_COLUMN_WIDTH;
|
|
19
|
+
const available = Math.max(0, width + gap);
|
|
20
|
+
const adaptive = Math.max(1, Math.floor(available / (minimum + gap)));
|
|
21
|
+
return Math.min(adaptive, normalizeMasonryColumnCount(maxColumns, MAX_COLUMN_COUNT));
|
|
22
|
+
}
|
|
23
|
+
export function calculateMasonryLayout(options) {
|
|
24
|
+
const containerWidth = Number.isFinite(options.containerWidth)
|
|
25
|
+
? Math.max(0, options.containerWidth)
|
|
26
|
+
: 0;
|
|
27
|
+
const columnGap = normalizeMasonrySpacing(options.columnGap);
|
|
28
|
+
const rowGap = normalizeMasonrySpacing(options.rowGap);
|
|
29
|
+
const requestedColumnCount = resolveMasonryColumnCount({
|
|
30
|
+
columns: options.columns,
|
|
31
|
+
containerWidth,
|
|
32
|
+
columnGap,
|
|
33
|
+
maxColumns: options.maxColumns,
|
|
34
|
+
minColumnWidth: options.minColumnWidth,
|
|
35
|
+
});
|
|
36
|
+
const columnCount = options.columns === undefined && options.items.length > 0
|
|
37
|
+
? Math.min(requestedColumnCount, options.items.length)
|
|
38
|
+
: requestedColumnCount;
|
|
39
|
+
const effectiveColumnGap = columnCount > 1
|
|
40
|
+
? Math.min(columnGap, containerWidth / (columnCount - 1))
|
|
41
|
+
: 0;
|
|
42
|
+
const columnWidth = Math.max(0, (containerWidth - effectiveColumnGap * (columnCount - 1)) / columnCount);
|
|
43
|
+
const columnHeights = Array.from({ length: columnCount }, () => 0);
|
|
44
|
+
const strategy = options.strategy ?? 'shortest';
|
|
45
|
+
const direction = options.direction ?? 'ltr';
|
|
46
|
+
const positions = options.items.map((item, index) => {
|
|
47
|
+
let column = strategy === 'shortest' ? 0 : index % columnCount;
|
|
48
|
+
if (strategy === 'shortest') {
|
|
49
|
+
for (let candidate = 1; candidate < columnCount; candidate += 1) {
|
|
50
|
+
if (columnHeights[candidate] < columnHeights[column])
|
|
51
|
+
column = candidate;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const height = Number.isFinite(item.height) ? Math.max(0, item.height) : 0;
|
|
55
|
+
const y = columnHeights[column] === 0 ? 0 : columnHeights[column] + rowGap;
|
|
56
|
+
const logicalX = column * (columnWidth + effectiveColumnGap);
|
|
57
|
+
const x = direction === 'rtl' ? containerWidth - columnWidth - logicalX : logicalX;
|
|
58
|
+
columnHeights[column] = y + height;
|
|
59
|
+
return { key: item.key, column, x, y, width: columnWidth, height };
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
columnCount,
|
|
63
|
+
columnWidth,
|
|
64
|
+
height: Math.max(0, ...columnHeights),
|
|
65
|
+
positions,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { PencilText } from '../PencilText.js';
|
|
3
|
+
import { rollMix } from './rolling.js';
|
|
4
|
+
export function RollingValue({ columns, progress, direction }) {
|
|
5
|
+
return _jsx("span", { className: "inkra-number-ticker__reels", dir: "ltr", "data-direction": direction, children: columns.map(column => {
|
|
6
|
+
const width = rollMix(column.fromWidth, column.toWidth, progress);
|
|
7
|
+
return _jsx("span", { className: "inkra-number-ticker__column", "data-place": column.key, "data-digit": column.digit || undefined, "data-departing": column.target === '' || undefined, style: column.digit ? { width: `${width}ch` } : { gridTemplateColumns: `${width}fr` }, children: _jsx("span", { className: "inkra-number-ticker__column-inner", children: column.faces.map(face => _jsx("span", { className: "inkra-number-ticker__face", "data-face": face.text === column.target ? 'to' : 'from', style: { transform: column.digit ? `translateY(${rollMix(face.fromOffset, face.toOffset, progress) * 100}%)` : undefined,
|
|
8
|
+
opacity: rollMix(face.fromOpacity, face.toOpacity, progress) }, children: _jsx(PencilText, { seed: `ticker-reel-${column.key}-${face.text}`, children: face.text }) }, face.text)) }) }, column.key);
|
|
9
|
+
}) });
|
|
10
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
2
2
|
import type { NumberTickerCompact, NumberTickerCompactOptions, NumberTickerFormatOptions, NumberTickerLocale, NumberTickerValue } from './model.js';
|
|
3
|
+
export type NumberTickerAnimation = 'count' | 'roll';
|
|
4
|
+
export type NumberTickerRollDirection = 'auto' | 'up' | 'down';
|
|
3
5
|
export interface NumberTickerRenderDetails {
|
|
4
6
|
animating: boolean;
|
|
5
7
|
formattedValue: string;
|
|
@@ -19,6 +21,8 @@ export interface NumberTickerProps extends Omit<ComponentPropsWithoutRef<'span'>
|
|
|
19
21
|
renderValue?: (value: NumberTickerValue, details: NumberTickerRenderDetails) => ReactNode;
|
|
20
22
|
getValueLabel?: (value: NumberTickerValue, formattedValue: string) => string;
|
|
21
23
|
animate?: boolean;
|
|
24
|
+
animation?: NumberTickerAnimation;
|
|
25
|
+
rollDirection?: NumberTickerRollDirection;
|
|
22
26
|
duration?: number;
|
|
23
27
|
easing?: (progress: number) => number;
|
|
24
28
|
showDelta?: boolean;
|
|
@@ -5,6 +5,8 @@ import { PencilText } from '../PencilText.js';
|
|
|
5
5
|
import { useBrowserLayoutEffect } from '../internal/useBrowserLayoutEffect.js';
|
|
6
6
|
import { canAnimateTickerValue, createNumberFormatter, createTickerFormatter, easeOutCubic, formatDerivedTickerDelta, formatTickerValue, interpolateTickerValue, tickerValueChanged, subtractTickerValues, tickerValueKey, } from './model.js';
|
|
7
7
|
import { ExactDecimal } from './precision.js';
|
|
8
|
+
import { RollingValue } from './RollingValue.js';
|
|
9
|
+
import { createRollingMotion, rollingTokens, sameRollingTokens } from './rolling.js';
|
|
8
10
|
function usePrefersReducedMotion() {
|
|
9
11
|
const [reduced, setReduced] = useState(false);
|
|
10
12
|
useBrowserLayoutEffect(() => {
|
|
@@ -24,7 +26,7 @@ function lettering(content, seed) {
|
|
|
24
26
|
function accessibleAffix(content) {
|
|
25
27
|
return typeof content === 'string' || typeof content === 'number' ? String(content) : '';
|
|
26
28
|
}
|
|
27
|
-
export const NumberTicker = forwardRef(function NumberTicker({ animate = true, className, compact = false, compactOptions, deltaDuration = 1600, deltaFormatOptions, duration = 420, easing = easeOutCubic, formatOptions, getValueLabel, locale: localeProp, prefix, renderDelta, renderValue, showDelta = false, suffix, value, ...props }, forwardedRef) {
|
|
29
|
+
export const NumberTicker = forwardRef(function NumberTicker({ animate = true, animation = 'count', className, compact = false, compactOptions, deltaDuration = 1600, deltaFormatOptions, duration = 420, easing = easeOutCubic, formatOptions, getValueLabel, locale: localeProp, prefix, renderDelta, renderValue, rollDirection = 'auto', showDelta = false, suffix, value, ...props }, forwardedRef) {
|
|
28
30
|
const { locale: inheritedLocale } = useInkraLocale();
|
|
29
31
|
const locale = localeProp ?? inheritedLocale;
|
|
30
32
|
const rootRef = useRef(null);
|
|
@@ -36,6 +38,9 @@ export const NumberTicker = forwardRef(function NumberTicker({ animate = true, c
|
|
|
36
38
|
const [announcedValue, setAnnouncedValue] = useState(value);
|
|
37
39
|
const [animating, setAnimating] = useState(false);
|
|
38
40
|
const [delta, setDelta] = useState(null);
|
|
41
|
+
const [roll, setRoll] = useState(null);
|
|
42
|
+
const [rollProgress, setRollProgress] = useState(1);
|
|
43
|
+
const rollFrameRef = useRef(null);
|
|
39
44
|
const reducedMotion = usePrefersReducedMotion();
|
|
40
45
|
const localeKey = Array.isArray(locale) ? locale.join('\u0000') : locale;
|
|
41
46
|
const formatOptionsKey = JSON.stringify(formatOptions ?? {});
|
|
@@ -45,6 +50,14 @@ export const NumberTicker = forwardRef(function NumberTicker({ animate = true, c
|
|
|
45
50
|
const deltaFormatter = useMemo(() => createTickerFormatter(locale, deltaFormatOptions ?? formatOptions, compact, compactOptions), [localeKey, deltaFormatOptionsKey, compact, compactOptionsKey]);
|
|
46
51
|
const announcementFormatter = useMemo(() => createNumberFormatter(locale, compact ? { ...formatOptions, notation: 'standard' } : formatOptions), [localeKey, formatOptionsKey, compact]);
|
|
47
52
|
useImperativeHandle(forwardedRef, () => rootRef.current);
|
|
53
|
+
const rollRequested = animation === 'roll' && !renderValue;
|
|
54
|
+
const finalTokens = useMemo(() => rollRequested ? rollingTokens(formatter.formatToParts(value)) : [], [formatter, value, rollRequested]);
|
|
55
|
+
// Keep exact large values readable without creating thousands of painted reels.
|
|
56
|
+
const useRoll = rollRequested && finalTokens.length <= 128;
|
|
57
|
+
const settledTokensRef = useRef(finalTokens);
|
|
58
|
+
const previousFormatterRef = useRef(formatter);
|
|
59
|
+
const previousUseRollRef = useRef(useRoll);
|
|
60
|
+
const columns = useMemo(() => roll?.columns ?? createRollingMotion(finalTokens, finalTokens, 'up'), [roll, finalTokens]);
|
|
48
61
|
useBrowserLayoutEffect(() => {
|
|
49
62
|
const previousTarget = targetRef.current;
|
|
50
63
|
targetRef.current = value;
|
|
@@ -72,22 +85,53 @@ export const NumberTicker = forwardRef(function NumberTicker({ animate = true, c
|
|
|
72
85
|
&& typeof window.matchMedia === 'function'
|
|
73
86
|
&& window.matchMedia('(prefers-reduced-motion: reduce)').matches);
|
|
74
87
|
const from = displayedRef.current;
|
|
75
|
-
|
|
88
|
+
const formatChanged = previousFormatterRef.current !== formatter;
|
|
89
|
+
previousFormatterRef.current = formatter;
|
|
90
|
+
const modeChanged = previousUseRollRef.current !== useRoll;
|
|
91
|
+
previousUseRollRef.current = useRoll;
|
|
92
|
+
if (!animate || reduceImmediately || formatChanged || modeChanged
|
|
93
|
+
|| (useRoll && !rollFrameRef.current && sameRollingTokens(settledTokensRef.current, finalTokens))
|
|
94
|
+
|| (rollRequested && (!useRoll || settledTokensRef.current.length > 128))
|
|
95
|
+
|| !canAnimateTickerValue(from, value, duration)) {
|
|
76
96
|
displayedRef.current = value;
|
|
77
97
|
setDisplayedValue(value);
|
|
78
98
|
setAnnouncedValue(value);
|
|
79
99
|
setAnimating(false);
|
|
100
|
+
setRoll(null);
|
|
101
|
+
setRollProgress(1);
|
|
102
|
+
settledTokensRef.current = finalTokens;
|
|
103
|
+
rollFrameRef.current = null;
|
|
80
104
|
return undefined;
|
|
81
105
|
}
|
|
106
|
+
if (useRoll) {
|
|
107
|
+
const direction = rollDirection === 'auto'
|
|
108
|
+
? (new ExactDecimal(String(value)).gt(String(from)) ? 'up' : 'down') : rollDirection;
|
|
109
|
+
const nextColumns = createRollingMotion(settledTokensRef.current, finalTokens, direction, rollFrameRef.current ?? undefined);
|
|
110
|
+
setRoll({ columns: nextColumns, direction });
|
|
111
|
+
setRollProgress(0);
|
|
112
|
+
rollFrameRef.current = { columns: nextColumns, progress: 0 };
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
setRoll(null);
|
|
116
|
+
rollFrameRef.current = null;
|
|
117
|
+
}
|
|
82
118
|
let startTime = null;
|
|
83
119
|
setAnimating(true);
|
|
84
120
|
const tick = (time) => {
|
|
85
121
|
if (startTime === null)
|
|
86
122
|
startTime = time;
|
|
87
123
|
const progress = Math.min(1, Math.max(0, (time - startTime) / duration));
|
|
88
|
-
const
|
|
124
|
+
const eased = easing(progress);
|
|
125
|
+
const bounded = Number.isFinite(eased) ? Math.min(1, Math.max(0, eased)) : progress;
|
|
126
|
+
const next = interpolateTickerValue(from, value, bounded);
|
|
89
127
|
displayedRef.current = next;
|
|
90
|
-
|
|
128
|
+
if (useRoll) {
|
|
129
|
+
setRollProgress(bounded);
|
|
130
|
+
if (rollFrameRef.current)
|
|
131
|
+
rollFrameRef.current.progress = bounded;
|
|
132
|
+
}
|
|
133
|
+
else
|
|
134
|
+
setDisplayedValue(next);
|
|
91
135
|
if (progress < 1) {
|
|
92
136
|
frameRef.current = requestAnimationFrame(tick);
|
|
93
137
|
}
|
|
@@ -97,6 +141,10 @@ export const NumberTicker = forwardRef(function NumberTicker({ animate = true, c
|
|
|
97
141
|
setDisplayedValue(value);
|
|
98
142
|
setAnnouncedValue(value);
|
|
99
143
|
setAnimating(false);
|
|
144
|
+
setRoll(null);
|
|
145
|
+
setRollProgress(1);
|
|
146
|
+
settledTokensRef.current = finalTokens;
|
|
147
|
+
rollFrameRef.current = null;
|
|
100
148
|
}
|
|
101
149
|
};
|
|
102
150
|
frameRef.current = requestAnimationFrame(tick);
|
|
@@ -106,7 +154,7 @@ export const NumberTicker = forwardRef(function NumberTicker({ animate = true, c
|
|
|
106
154
|
frameRef.current = null;
|
|
107
155
|
}
|
|
108
156
|
};
|
|
109
|
-
}, [animate, deltaDuration, duration, easing, reducedMotion, showDelta, value]);
|
|
157
|
+
}, [animate, deltaDuration, duration, easing, reducedMotion, showDelta, value, useRoll, rollRequested, rollDirection, formatter]);
|
|
110
158
|
useEffect(() => () => {
|
|
111
159
|
if (frameRef.current !== null)
|
|
112
160
|
cancelAnimationFrame(frameRef.current);
|
|
@@ -120,7 +168,7 @@ export const NumberTicker = forwardRef(function NumberTicker({ animate = true, c
|
|
|
120
168
|
.filter(Boolean).join(' ');
|
|
121
169
|
const accessibleLabel = getValueLabel?.(announcedValue, announcedFormattedValue) ?? defaultAccessibleLabel;
|
|
122
170
|
const valueContent = renderValue?.(displayedValue, { animating, formattedValue })
|
|
123
|
-
?? (animating
|
|
171
|
+
?? (useRoll ? _jsx(RollingValue, { columns: columns, progress: roll ? rollProgress : 1, direction: roll?.direction ?? 'up' }) : animating
|
|
124
172
|
? _jsx("span", { className: "inkra-number-ticker__native-value", children: formattedValue })
|
|
125
173
|
: _jsx(PencilText, { seed: `number-ticker-${finalFormattedValue}`, children: finalFormattedValue }));
|
|
126
174
|
const deltaContent = delta === null ? null : (() => {
|
|
@@ -133,6 +181,6 @@ export const NumberTicker = forwardRef(function NumberTicker({ animate = true, c
|
|
|
133
181
|
})();
|
|
134
182
|
const classes = ['inkra-number-ticker', animating && 'inkra-number-ticker--animating', className]
|
|
135
183
|
.filter(Boolean).join(' ');
|
|
136
|
-
return _jsxs("span", { ...props, className: classes, ref: rootRef, "data-animating": animating || undefined, children: [_jsxs("span", { "aria-hidden": "true", className: "inkra-number-ticker__visual", children: [prefix !== undefined && _jsx("span", { className: "inkra-number-ticker__prefix", children: lettering(prefix, 'number-ticker-prefix') }), _jsx("span", { className: "inkra-number-ticker__value", children: valueContent }), suffix !== undefined && _jsx("span", { className: "inkra-number-ticker__suffix", children: lettering(suffix, 'number-ticker-suffix') }), deltaContent !== null && _jsx("span", { className: "inkra-number-ticker__delta", "data-direction": new ExactDecimal(String(delta)).lt(0) ? 'decrease' : 'increase', children: deltaContent })] }), _jsx("span", { className: "inkra-number-ticker__announcement", "aria-atomic": "true", "aria-live": "polite", children: accessibleLabel })] });
|
|
184
|
+
return _jsxs("span", { ...props, className: classes, ref: rootRef, "data-animating": animating || undefined, "data-animation": animation, children: [_jsxs("span", { "aria-hidden": "true", className: "inkra-number-ticker__visual", children: [prefix !== undefined && _jsx("span", { className: "inkra-number-ticker__prefix", children: lettering(prefix, 'number-ticker-prefix') }), _jsx("span", { className: "inkra-number-ticker__value", children: valueContent }), suffix !== undefined && _jsx("span", { className: "inkra-number-ticker__suffix", children: lettering(suffix, 'number-ticker-suffix') }), deltaContent !== null && _jsx("span", { className: "inkra-number-ticker__delta", "data-direction": new ExactDecimal(String(delta)).lt(0) ? 'decrease' : 'increase', children: deltaContent })] }), _jsx("span", { className: "inkra-number-ticker__announcement", "aria-atomic": "true", "aria-live": "polite", children: accessibleLabel })] });
|
|
137
185
|
});
|
|
138
186
|
export { createNumberFormatter, easeOutCubic, formatTickerDelta, formatTickerValue, interpolateTickerValue, numberTickerUnits, } from './model.js';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface RollingToken {
|
|
2
|
+
key: string;
|
|
3
|
+
text: string;
|
|
4
|
+
digit: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function sameRollingTokens(from: readonly RollingToken[], to: readonly RollingToken[]): boolean;
|
|
7
|
+
export interface RollingColumn {
|
|
8
|
+
key: string;
|
|
9
|
+
from: string;
|
|
10
|
+
to: string;
|
|
11
|
+
digit: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface RollingFace {
|
|
14
|
+
text: string;
|
|
15
|
+
fromOffset: number;
|
|
16
|
+
toOffset: number;
|
|
17
|
+
fromOpacity: number;
|
|
18
|
+
toOpacity: number;
|
|
19
|
+
}
|
|
20
|
+
export interface RollingMotionColumn {
|
|
21
|
+
key: string;
|
|
22
|
+
digit: boolean;
|
|
23
|
+
target: string;
|
|
24
|
+
fromWidth: number;
|
|
25
|
+
toWidth: number;
|
|
26
|
+
faces: RollingFace[];
|
|
27
|
+
}
|
|
28
|
+
export interface RollingFrame {
|
|
29
|
+
columns: RollingMotionColumn[];
|
|
30
|
+
progress: number;
|
|
31
|
+
}
|
|
32
|
+
export declare const rollMix: (from: number, to: number, progress: number) => number;
|
|
33
|
+
export declare function createRollingMotion(from: RollingToken[], to: RollingToken[], direction: 'up' | 'down', previous?: RollingFrame): RollingMotionColumn[];
|
|
34
|
+
export declare function rollingTokens(parts: Intl.NumberFormatPart[]): RollingToken[];
|
|
35
|
+
export declare function rollingColumns(from: RollingToken[], to: RollingToken[]): RollingColumn[];
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export function sameRollingTokens(from, to) {
|
|
2
|
+
return from.length === to.length && from.every((token, index) => {
|
|
3
|
+
const next = to[index];
|
|
4
|
+
return token.key === next.key && token.text === next.text && token.digit === next.digit;
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
export const rollMix = (from, to, progress) => from + (to - from) * progress;
|
|
8
|
+
export function createRollingMotion(from, to, direction, previous) {
|
|
9
|
+
const sign = direction === 'up' ? -1 : 1;
|
|
10
|
+
const snapshot = previous?.columns.map(column => ({
|
|
11
|
+
key: column.key, digit: column.digit,
|
|
12
|
+
width: rollMix(column.fromWidth, column.toWidth, previous.progress),
|
|
13
|
+
faces: column.faces.map(face => ({
|
|
14
|
+
text: face.text,
|
|
15
|
+
offset: rollMix(face.fromOffset, face.toOffset, previous.progress),
|
|
16
|
+
opacity: rollMix(face.fromOpacity, face.toOpacity, previous.progress),
|
|
17
|
+
})).filter(face => Math.abs(face.offset) < 1 && face.opacity > 0.01),
|
|
18
|
+
})) ?? from.map(token => ({ key: token.key, digit: token.digit, width: 1, faces: [{ text: token.text, offset: 0, opacity: 1 }] }));
|
|
19
|
+
const sources = new Map(snapshot.map(column => [column.key, column]));
|
|
20
|
+
const sourceTokens = snapshot.filter(column => column.width > 0 || column.faces.length > 0)
|
|
21
|
+
.map(column => ({ key: column.key, digit: column.digit, text: column.faces[0]?.text ?? '' }));
|
|
22
|
+
return rollingColumns(sourceTokens, to).map(column => {
|
|
23
|
+
const source = sources.get(column.key);
|
|
24
|
+
const faces = [...(source?.faces ?? [])];
|
|
25
|
+
if (column.to !== '' && !faces.some(face => face.text === column.to)) {
|
|
26
|
+
faces.push({ text: column.to, offset: column.digit ? -sign : 0, opacity: column.digit ? 1 : 0 });
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
key: column.key, digit: column.digit, target: column.to,
|
|
30
|
+
fromWidth: source?.width ?? 0, toWidth: column.to === '' ? 0 : 1,
|
|
31
|
+
// Existing faces continue from their actual offset on a rapid retarget.
|
|
32
|
+
faces: faces.map(face => ({ text: face.text, fromOffset: face.offset,
|
|
33
|
+
toOffset: face.text === column.to ? 0 : column.digit ? sign : 0,
|
|
34
|
+
fromOpacity: face.opacity, toOpacity: face.text === column.to || column.digit ? 1 : 0,
|
|
35
|
+
})),
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// Align integer places from the right and fractional places from the decimal.
|
|
40
|
+
// Intl parts keep signs, grouping, currency and compact units out of digit reels.
|
|
41
|
+
export function rollingTokens(parts) {
|
|
42
|
+
const remaining = new Map();
|
|
43
|
+
for (const part of parts) {
|
|
44
|
+
if (part.type === 'integer' || part.type === 'exponentInteger') {
|
|
45
|
+
remaining.set(part.type, (remaining.get(part.type) ?? 0) + Array.from(part.value).length);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const occurrences = new Map();
|
|
49
|
+
return parts.flatMap((part, index) => {
|
|
50
|
+
if (part.type === 'integer' || part.type === 'exponentInteger') {
|
|
51
|
+
return Array.from(part.value, text => {
|
|
52
|
+
const position = (remaining.get(part.type) ?? 1) - 1;
|
|
53
|
+
remaining.set(part.type, position);
|
|
54
|
+
return { key: `${part.type}-${position}`, text, digit: true };
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (part.type === 'fraction') {
|
|
58
|
+
return Array.from(part.value, (text, position) => ({ key: `fraction-${position}`, text, digit: true }));
|
|
59
|
+
}
|
|
60
|
+
const identity = part.type === 'literal'
|
|
61
|
+
? `literal-${part.value}-${parts.slice(index + 1).find(next => next.type !== 'literal')?.type ?? 'end'}`
|
|
62
|
+
: part.type;
|
|
63
|
+
const occurrence = occurrences.get(identity) ?? 0;
|
|
64
|
+
occurrences.set(identity, occurrence + 1);
|
|
65
|
+
const key = part.type === 'group' ? `group-${remaining.get('integer')}` : `${identity}-${occurrence}`;
|
|
66
|
+
return [{ key, text: part.value, digit: false }];
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
export function rollingColumns(from, to) {
|
|
70
|
+
const old = new Map(from.map(token => [token.key, token]));
|
|
71
|
+
const next = new Map(to.map(token => [token.key, token]));
|
|
72
|
+
const result = to.map(token => ({
|
|
73
|
+
key: token.key, from: old.get(token.key)?.text ?? '', to: token.text, digit: token.digit,
|
|
74
|
+
}));
|
|
75
|
+
// Departing places retain their old order until their width reaches zero.
|
|
76
|
+
for (let index = from.length - 1; index >= 0; index -= 1) {
|
|
77
|
+
const token = from[index];
|
|
78
|
+
if (next.has(token.key))
|
|
79
|
+
continue;
|
|
80
|
+
const following = from[index + 1];
|
|
81
|
+
const insertion = following ? result.findIndex(column => column.key === following.key) : -1;
|
|
82
|
+
result.splice(insertion < 0 ? result.length : insertion, 0, {
|
|
83
|
+
key: token.key, from: token.text, to: '', digit: token.digit,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export declare function CircularSwitchVisual({ checked, diameter, centerIcon, centerContent, seed }: {
|
|
3
|
+
checked: boolean;
|
|
4
|
+
diameter: number;
|
|
5
|
+
centerIcon?: ReactNode;
|
|
6
|
+
centerContent?: ReactNode | ((checked: boolean) => ReactNode);
|
|
7
|
+
seed: string | number;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { PencilSurface } from '../PencilSurface.js';
|
|
4
|
+
import { PencilText } from '../PencilText.js';
|
|
5
|
+
import { ProgressRing } from '../display/ProgressRing.js';
|
|
6
|
+
import { useInkraLocale } from '../i18n/index.js';
|
|
7
|
+
/** Animate only the clipping percentage; ProgressRing retains its full pencil texture. */
|
|
8
|
+
function useSwitchArcProgress(checked) {
|
|
9
|
+
const target = checked ? 75 : 0;
|
|
10
|
+
const [progress, setProgress] = useState(target);
|
|
11
|
+
const progressRef = useRef(target);
|
|
12
|
+
const targetRef = useRef(target);
|
|
13
|
+
const frameRef = useRef(null);
|
|
14
|
+
const mediaRef = useRef(null);
|
|
15
|
+
targetRef.current = target;
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const media = typeof window !== 'undefined' && typeof window.matchMedia === 'function'
|
|
18
|
+
? window.matchMedia('(prefers-reduced-motion: reduce)') : null;
|
|
19
|
+
mediaRef.current = media;
|
|
20
|
+
const change = () => {
|
|
21
|
+
if (!media?.matches)
|
|
22
|
+
return;
|
|
23
|
+
if (frameRef.current !== null)
|
|
24
|
+
cancelAnimationFrame(frameRef.current);
|
|
25
|
+
frameRef.current = null;
|
|
26
|
+
progressRef.current = targetRef.current;
|
|
27
|
+
setProgress(targetRef.current);
|
|
28
|
+
};
|
|
29
|
+
media?.addEventListener('change', change);
|
|
30
|
+
return () => {
|
|
31
|
+
media?.removeEventListener('change', change);
|
|
32
|
+
if (frameRef.current !== null)
|
|
33
|
+
cancelAnimationFrame(frameRef.current);
|
|
34
|
+
frameRef.current = null;
|
|
35
|
+
};
|
|
36
|
+
}, []);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (progressRef.current === target)
|
|
39
|
+
return;
|
|
40
|
+
if (mediaRef.current?.matches || typeof requestAnimationFrame !== 'function') {
|
|
41
|
+
progressRef.current = target;
|
|
42
|
+
setProgress(target);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const from = progressRef.current;
|
|
46
|
+
let start;
|
|
47
|
+
const tick = (time) => {
|
|
48
|
+
frameRef.current = null;
|
|
49
|
+
if (targetRef.current !== target)
|
|
50
|
+
return;
|
|
51
|
+
start ?? (start = time);
|
|
52
|
+
const fraction = Math.min(1, Math.max(0, (time - start) / 200));
|
|
53
|
+
const eased = fraction * fraction * (3 - 2 * fraction);
|
|
54
|
+
const next = from + (target - from) * eased;
|
|
55
|
+
progressRef.current = next;
|
|
56
|
+
setProgress(next);
|
|
57
|
+
if (fraction < 1)
|
|
58
|
+
frameRef.current = requestAnimationFrame(tick);
|
|
59
|
+
};
|
|
60
|
+
frameRef.current = requestAnimationFrame(tick);
|
|
61
|
+
return () => {
|
|
62
|
+
if (frameRef.current !== null)
|
|
63
|
+
cancelAnimationFrame(frameRef.current);
|
|
64
|
+
frameRef.current = null;
|
|
65
|
+
};
|
|
66
|
+
}, [target]);
|
|
67
|
+
return progress;
|
|
68
|
+
}
|
|
69
|
+
export function CircularSwitchVisual({ checked, diameter, centerIcon, centerContent, seed }) {
|
|
70
|
+
const { t } = useInkraLocale();
|
|
71
|
+
const progress = useSwitchArcProgress(checked);
|
|
72
|
+
const radius = diameter / 2 - 25;
|
|
73
|
+
const content = centerContent === undefined ? t(checked ? 'switch.on' : 'switch.off') : typeof centerContent === 'function' ? centerContent(checked) : centerContent;
|
|
74
|
+
const hasIcon = centerIcon != null && typeof centerIcon !== 'boolean';
|
|
75
|
+
const hasContent = content != null && typeof content !== 'boolean';
|
|
76
|
+
const height = (radius - 7) * 2;
|
|
77
|
+
const combined = hasIcon && hasContent;
|
|
78
|
+
const iconSize = combined ? Math.min(24, Math.max(12, 16 + (diameter - 104) / 4), height - 20) : 24;
|
|
79
|
+
const lineHeight = Math.min(diameter <= 112 ? 18 : 24, height - iconSize - 6);
|
|
80
|
+
return _jsxs("span", { className: "inkra-switch__circular", "aria-hidden": "true", style: { '--inkra-circular-switch-diameter': `${diameter}px`, '--inkra-circular-switch-center-width': `${(radius - 9) * 2 + 10}px`, '--inkra-circular-switch-center-height': `${height + 6}px`, '--inkra-circular-switch-icon-size': `${iconSize}px`, '--inkra-circular-switch-value-size': combined ? lineHeight < 18 ? '12px' : lineHeight < 22 ? '14px' : 'var(--inkra-text-lg)' : 'var(--inkra-text-lg)', '--inkra-circular-switch-value-line': combined ? `${lineHeight}px` : 'var(--inkra-line-ui)' }, children: [_jsxs("span", { className: "inkra-switch__circular-arc", children: [_jsx("span", { className: "inkra-switch__circular-track", children: _jsx(ProgressRing, { diameter: diameter - 32, thickness: 5, percent: 75, seed: `${seed}-track` }) }), _jsx("span", { className: "inkra-switch__circular-range", children: _jsx(ProgressRing, { diameter: diameter - 32, thickness: 5, percent: progress, seed: `${seed}-range` }) })] }), (hasIcon || hasContent) && _jsxs("span", { className: "inkra-switch__circular-center", inert: true, children: [hasIcon && _jsx("span", { className: "inkra-switch__circular-icon", children: centerIcon }), hasContent && _jsx("span", { className: "inkra-switch__circular-content", children: typeof content === 'string' || typeof content === 'number'
|
|
81
|
+
? _jsx(PencilText, { className: "inkra-switch__circular-value", ellipsis: centerContent !== undefined, children: String(content) }) : content })] }), _jsx(PencilSurface, { as: "span", className: "inkra-switch__circular-focus", variant: "outline", radius: diameter / 2, seed: `${seed}-focus` })] });
|
|
82
|
+
}
|
|
@@ -2,6 +2,13 @@ import type { InputHTMLAttributes, ReactNode } from 'react';
|
|
|
2
2
|
export interface SwitchProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'children' | 'role' | 'type'> {
|
|
3
3
|
label: ReactNode;
|
|
4
4
|
description?: ReactNode;
|
|
5
|
+
variant?: 'standard' | 'circular';
|
|
6
|
+
/** Circular variant outer size in CSS pixels, bounded to 96–512. */
|
|
7
|
+
diameter?: number;
|
|
8
|
+
/** Decorative, non-interactive icon for the circular center. */
|
|
9
|
+
centerIcon?: ReactNode;
|
|
10
|
+
/** Circular state label override. null/false hide it; nodes must be non-interactive. */
|
|
11
|
+
centerContent?: ReactNode | ((checked: boolean) => ReactNode);
|
|
5
12
|
onCheckedChange?: (checked: boolean) => void;
|
|
6
13
|
seed?: string | number;
|
|
7
14
|
}
|
package/dist/selection/Switch.js
CHANGED
|
@@ -4,6 +4,7 @@ import { PencilIcon } from '../PencilIcon.js';
|
|
|
4
4
|
import { PencilSurface } from '../PencilSurface.js';
|
|
5
5
|
import { PencilText } from '../PencilText.js';
|
|
6
6
|
import { useNativeChecked } from './useNativeChecked.js';
|
|
7
|
+
import { CircularSwitchVisual } from './CircularSwitchVisual.js';
|
|
7
8
|
const thumbPaths = [
|
|
8
9
|
{ d: 'M 20 3 C 30 3 37 10 37 20 C 37 30 30 37 20 37 C 10 37 3 30 3 20 C 3 10 10 3 20 3 Z', mode: 'wash' },
|
|
9
10
|
{ d: 'M 20 3 C 30 3 37 10 37 20 C 37 30 30 37 20 37 C 10 37 3 30 3 20 C 3 10 10 3 20 3 Z' },
|
|
@@ -12,7 +13,7 @@ function ids(...values) {
|
|
|
12
13
|
const value = values.filter(Boolean).join(' ');
|
|
13
14
|
return value || undefined;
|
|
14
15
|
}
|
|
15
|
-
export const Switch = forwardRef(function Switch({ 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, checked, className, defaultChecked, description, disabled = false, form, id, label, onChange, onCheckedChange, seed, ...props }, forwardedRef) {
|
|
16
|
+
export const Switch = forwardRef(function Switch({ 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, checked, className, defaultChecked, description, disabled = false, readOnly = false, variant = 'standard', diameter = 128, centerIcon, centerContent, form, id, label, onChange, onCheckedChange, onClick, seed, ...props }, forwardedRef) {
|
|
16
17
|
const generatedId = useId();
|
|
17
18
|
const inputId = id ?? `inkra-switch-${generatedId}`;
|
|
18
19
|
const labelId = `${inputId}-label`;
|
|
@@ -20,6 +21,7 @@ export const Switch = forwardRef(function Switch({ 'aria-label': ariaLabel, 'ari
|
|
|
20
21
|
const resolvedLabelledBy = ariaLabel == null && ariaLabelledBy == null ? labelId : ariaLabelledBy;
|
|
21
22
|
const resolvedSeed = seed ?? inputId;
|
|
22
23
|
const inputRef = useRef(null);
|
|
24
|
+
const resolvedDiameter = Math.min(512, Math.max(96, Number.isFinite(diameter) ? diameter : 128));
|
|
23
25
|
useImperativeHandle(forwardedRef, () => inputRef.current);
|
|
24
26
|
const { controlled, syncUncontrolled, visualChecked } = useNativeChecked({
|
|
25
27
|
checked,
|
|
@@ -27,11 +29,21 @@ export const Switch = forwardRef(function Switch({ 'aria-label': ariaLabel, 'ari
|
|
|
27
29
|
form,
|
|
28
30
|
inputRef,
|
|
29
31
|
});
|
|
30
|
-
return (_jsxs("label", { className: ['inkra-selection-control', 'inkra-switch', className].filter(Boolean).join(' '), "data-checked": visualChecked || undefined, "data-disabled": disabled || undefined, htmlFor: inputId, children: [_jsx("input", { ...props, "aria-describedby": ids(ariaDescribedBy, descriptionId), "aria-label": ariaLabel, "aria-labelledby": resolvedLabelledBy, checked: controlled ? checked : undefined, className: "inkra-selection-control__input", defaultChecked: controlled ? undefined : defaultChecked, disabled: disabled, form: form, id: inputId, onChange: (event) => {
|
|
32
|
+
return (_jsxs("label", { className: ['inkra-selection-control', 'inkra-switch', variant === 'circular' && 'inkra-switch--circular', className].filter(Boolean).join(' '), "data-variant": variant, "data-readonly": readOnly || undefined, style: variant === 'circular' ? { '--inkra-circular-switch-diameter': `${resolvedDiameter}px` } : undefined, "data-checked": visualChecked || undefined, "data-disabled": disabled || undefined, htmlFor: inputId, children: [_jsx("input", { ...props, "aria-describedby": ids(ariaDescribedBy, descriptionId), "aria-label": ariaLabel, "aria-labelledby": resolvedLabelledBy, checked: controlled ? checked : undefined, className: "inkra-selection-control__input", defaultChecked: controlled ? undefined : defaultChecked, disabled: disabled, readOnly: readOnly, "aria-readonly": readOnly || undefined, form: form, id: inputId, onChange: (event) => {
|
|
33
|
+
if (readOnly || event.nativeEvent?.defaultPrevented) {
|
|
34
|
+
event.currentTarget.checked = visualChecked;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
31
37
|
const nextChecked = event.currentTarget.checked;
|
|
32
38
|
if (!controlled)
|
|
33
39
|
syncUncontrolled(nextChecked);
|
|
34
40
|
onChange?.(event);
|
|
35
41
|
onCheckedChange?.(nextChecked);
|
|
36
|
-
},
|
|
42
|
+
}, onClick: event => {
|
|
43
|
+
if (readOnly) {
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
onClick?.(event);
|
|
48
|
+
}, ref: inputRef, role: "switch", type: "checkbox" }), variant === 'circular' ? _jsx(CircularSwitchVisual, { checked: visualChecked, diameter: resolvedDiameter, centerIcon: centerIcon, centerContent: centerContent, seed: resolvedSeed }) : _jsxs(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-switch__track", radius: 8, seed: `${resolvedSeed}-track`, variant: visualChecked ? 'quiet' : 'outline', children: [_jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-selection-control__focus-ring", seed: `${resolvedSeed}-focus`, variant: "outline" }), _jsx(PencilIcon, { className: "inkra-switch__thumb", paths: thumbPaths, seed: `${resolvedSeed}-thumb`, size: 24, sourceSize: 40 })] }), _jsxs("span", { className: "inkra-selection-control__copy", children: [typeof label === 'string' ? (_jsx(PencilText, { as: "span", className: "inkra-selection-control__label", id: labelId, seed: `${resolvedSeed}-label`, children: label })) : _jsx("span", { className: "inkra-selection-control__label", id: labelId, children: label }), description == null ? null : (_jsx("span", { className: "inkra-selection-control__description", id: descriptionId, children: description }))] })] }));
|
|
37
49
|
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
export interface CircularSliderProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'defaultValue' | 'onChange'> {
|
|
3
|
+
value?: number;
|
|
4
|
+
defaultValue?: number;
|
|
5
|
+
onValueChange?: (value: number) => void;
|
|
6
|
+
onValueCommit?: (value: number) => void;
|
|
7
|
+
min?: number;
|
|
8
|
+
max?: number;
|
|
9
|
+
step?: number;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
readOnly?: boolean;
|
|
12
|
+
name?: string;
|
|
13
|
+
form?: string;
|
|
14
|
+
size?: 'sm' | 'md' | 'lg';
|
|
15
|
+
/** Outer size in CSS pixels, bounded to 96–512. Defaults to 128/160/192 by size. */
|
|
16
|
+
diameter?: number;
|
|
17
|
+
/** Degrees from the top, increasing clockwise. Defaults to 225. */
|
|
18
|
+
startAngle?: number;
|
|
19
|
+
/** Available arc in degrees, from 1 to 360. Defaults to 270. */
|
|
20
|
+
sweepAngle?: number;
|
|
21
|
+
/** Physical direction of increasing values; unaffected by the page's text direction. */
|
|
22
|
+
clockwise?: boolean;
|
|
23
|
+
/** Shows the default value when centerContent is not specified. */
|
|
24
|
+
showValue?: boolean;
|
|
25
|
+
/** Decorative, non-interactive icon placed above the center content. */
|
|
26
|
+
centerIcon?: ReactNode;
|
|
27
|
+
/** Overrides the default value. Strings/numbers use PencilText; null/false hide it.
|
|
28
|
+
* Custom nodes must be non-interactive; the center is inert and hidden from accessibility APIs.
|
|
29
|
+
*/
|
|
30
|
+
centerContent?: ReactNode | ((value: number) => ReactNode);
|
|
31
|
+
/** Used for the center label and aria-valuetext. */
|
|
32
|
+
getValueLabel?: (value: number) => string;
|
|
33
|
+
seed?: string | number;
|
|
34
|
+
}
|
|
35
|
+
export declare const CircularSlider: import("react").ForwardRefExoticComponent<CircularSliderProps & import("react").RefAttributes<HTMLDivElement>>;
|