@wallarm-org/design-system 0.69.0-rc-feature-WDS-143.2 → 0.69.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/Attribute/index.d.ts +0 -8
- package/dist/components/Attribute/index.js +1 -9
- package/dist/components/Calendar/Calendar.js +4 -14
- package/dist/components/Calendar/CalendarContext.d.ts +2 -19
- package/dist/components/Calendar/CalendarHeader.js +1 -2
- package/dist/components/Calendar/CalendarInputHeader.js +27 -27
- package/dist/components/Slider/Slider.d.ts +34 -0
- package/dist/components/Slider/Slider.figma.d.ts +1 -0
- package/dist/components/Slider/Slider.figma.js +123 -0
- package/dist/components/Slider/Slider.js +85 -0
- package/dist/components/Slider/SliderContext.d.ts +31 -0
- package/dist/components/Slider/SliderContext.js +9 -0
- package/dist/components/Slider/SliderControl.d.ts +9 -0
- package/dist/components/Slider/SliderControl.js +35 -0
- package/dist/components/Slider/SliderInput.d.ts +26 -0
- package/dist/components/Slider/SliderInput.js +69 -0
- package/dist/components/Slider/SliderMarks.d.ts +15 -0
- package/dist/components/Slider/SliderMarks.js +58 -0
- package/dist/components/Slider/SliderThumb.d.ts +19 -0
- package/dist/components/Slider/SliderThumb.js +58 -0
- package/dist/components/Slider/SliderValue.d.ts +14 -0
- package/dist/components/Slider/SliderValue.js +21 -0
- package/dist/components/Slider/classes.d.ts +52 -0
- package/dist/components/Slider/classes.js +14 -0
- package/dist/components/Slider/index.d.ts +8 -0
- package/dist/components/Slider/index.js +8 -0
- package/dist/components/Slider/types.d.ts +11 -0
- package/dist/components/Slider/types.js +0 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -1
- package/dist/metadata/components.json +2439 -3630
- package/package.json +1 -1
- package/dist/components/Attribute/AttributeEdit.d.ts +0 -28
- package/dist/components/Attribute/AttributeEdit.js +0 -151
- package/dist/components/Attribute/AttributeEditContext.d.ts +0 -22
- package/dist/components/Attribute/AttributeEditContext.js +0 -9
- package/dist/components/Attribute/AttributeEditControl.d.ts +0 -6
- package/dist/components/Attribute/AttributeEditControl.js +0 -69
- package/dist/components/Attribute/AttributeEditError.d.ts +0 -6
- package/dist/components/Attribute/AttributeEditError.js +0 -32
- package/dist/components/Attribute/AttributeEditInput.d.ts +0 -4
- package/dist/components/Attribute/AttributeEditInput.js +0 -19
- package/dist/components/Attribute/AttributeEditNumber.d.ts +0 -11
- package/dist/components/Attribute/AttributeEditNumber.js +0 -17
- package/dist/components/Attribute/AttributeEditPreview.d.ts +0 -18
- package/dist/components/Attribute/AttributeEditPreview.js +0 -111
- package/dist/components/Attribute/AttributeEditTextarea.d.ts +0 -4
- package/dist/components/Attribute/AttributeEditTextarea.js +0 -17
- package/dist/components/Calendar/dateValue.d.ts +0 -18
- package/dist/components/Calendar/dateValue.js +0 -7
- package/dist/components/Calendar/useCalendarTime.d.ts +0 -25
- package/dist/components/Calendar/useCalendarTime.js +0 -38
|
@@ -3,14 +3,6 @@ export { AttributeActions, type AttributeActionsProps } from './AttributeActions
|
|
|
3
3
|
export { AttributeActionsContent, type AttributeActionsContentProps, } from './AttributeActionsContent';
|
|
4
4
|
export { AttributeActionsItem, type AttributeActionsItemProps } from './AttributeActionsItem';
|
|
5
5
|
export { AttributeActionsTarget, type AttributeActionsTargetProps } from './AttributeActionsTarget';
|
|
6
|
-
export { AttributeEdit, type AttributeEditProps } from './AttributeEdit';
|
|
7
|
-
export { type AttributeEditActivationMode, type AttributeEditContextValue, type AttributeEditStatus, type AttributeEditSubmitMode, useAttributeEdit, } from './AttributeEditContext';
|
|
8
|
-
export { AttributeEditControl, type AttributeEditControlProps } from './AttributeEditControl';
|
|
9
|
-
export { AttributeEditError, type AttributeEditErrorProps } from './AttributeEditError';
|
|
10
|
-
export { AttributeEditInput, type AttributeEditInputProps } from './AttributeEditInput';
|
|
11
|
-
export { AttributeEditNumber, type AttributeEditNumberProps } from './AttributeEditNumber';
|
|
12
|
-
export { AttributeEditPreview, type AttributeEditPreviewProps } from './AttributeEditPreview';
|
|
13
|
-
export { AttributeEditTextarea, type AttributeEditTextareaProps } from './AttributeEditTextarea';
|
|
14
6
|
export { useAttributeEmpty } from './AttributeEmptyContext';
|
|
15
7
|
export { AttributeEmptyDescription, type AttributeEmptyDescriptionProps, } from './AttributeEmptyDescription';
|
|
16
8
|
export { AttributeLabel, type AttributeLabelProps } from './AttributeLabel';
|
|
@@ -3,18 +3,10 @@ import { AttributeActions } from "./AttributeActions.js";
|
|
|
3
3
|
import { AttributeActionsContent } from "./AttributeActionsContent.js";
|
|
4
4
|
import { AttributeActionsItem } from "./AttributeActionsItem.js";
|
|
5
5
|
import { AttributeActionsTarget } from "./AttributeActionsTarget.js";
|
|
6
|
-
import { AttributeEdit } from "./AttributeEdit.js";
|
|
7
|
-
import { useAttributeEdit } from "./AttributeEditContext.js";
|
|
8
|
-
import { AttributeEditControl } from "./AttributeEditControl.js";
|
|
9
|
-
import { AttributeEditError } from "./AttributeEditError.js";
|
|
10
|
-
import { AttributeEditInput } from "./AttributeEditInput.js";
|
|
11
|
-
import { AttributeEditNumber } from "./AttributeEditNumber.js";
|
|
12
|
-
import { AttributeEditPreview } from "./AttributeEditPreview.js";
|
|
13
|
-
import { AttributeEditTextarea } from "./AttributeEditTextarea.js";
|
|
14
6
|
import { useAttributeEmpty } from "./AttributeEmptyContext.js";
|
|
15
7
|
import { AttributeEmptyDescription } from "./AttributeEmptyDescription.js";
|
|
16
8
|
import { AttributeLabel } from "./AttributeLabel.js";
|
|
17
9
|
import { AttributeLabelDescription } from "./AttributeLabelDescription.js";
|
|
18
10
|
import { AttributeLabelInfo } from "./AttributeLabelInfo.js";
|
|
19
11
|
import { AttributeValue } from "./AttributeValue.js";
|
|
20
|
-
export { Attribute, AttributeActions, AttributeActionsContent, AttributeActionsItem, AttributeActionsTarget,
|
|
12
|
+
export { Attribute, AttributeActions, AttributeActionsContent, AttributeActionsItem, AttributeActionsTarget, AttributeEmptyDescription, AttributeLabel, AttributeLabelDescription, AttributeLabelInfo, AttributeValue, useAttributeEmpty };
|
|
@@ -3,8 +3,7 @@ import { useCallback } from "react";
|
|
|
3
3
|
import { DatePicker } from "@ark-ui/react";
|
|
4
4
|
import { CalendarDate, endOfMonth, getLocalTimeZone, startOfMonth, today } from "@internationalized/date";
|
|
5
5
|
import { CalendarProvider } from "./CalendarContext.js";
|
|
6
|
-
|
|
7
|
-
import { useCalendarTime } from "./useCalendarTime.js";
|
|
6
|
+
const toDateValue = (date)=>date;
|
|
8
7
|
const getThisMonthRange = ()=>{
|
|
9
8
|
const now = today(getLocalTimeZone());
|
|
10
9
|
return [
|
|
@@ -116,13 +115,6 @@ const DEFAULT_RANGE_PRESETS = [
|
|
|
116
115
|
const Calendar = ({ type = 'single', showTime = false, showKeyboardHints = true, value, defaultValue, onChange, minDate, maxDate, isDateUnavailable, disallowDisabledDatesInRange = false, open, defaultOpen, onOpenChange, closeOnSelect = true, readonly = false, focusedValue, children })=>{
|
|
117
116
|
const isRange = 'range' === type;
|
|
118
117
|
const numOfMonths = isRange ? 2 : 1;
|
|
119
|
-
const { timeRef, commitValue } = useCalendarTime({
|
|
120
|
-
showTime,
|
|
121
|
-
isRange,
|
|
122
|
-
value,
|
|
123
|
-
defaultValue,
|
|
124
|
-
onChange
|
|
125
|
-
});
|
|
126
118
|
const hasUnavailableDateInRange = useCallback((start, end)=>{
|
|
127
119
|
if (!isDateUnavailable) return false;
|
|
128
120
|
let current = new CalendarDate(start.year, start.month, start.day);
|
|
@@ -151,9 +143,9 @@ const Calendar = ({ type = 'single', showTime = false, showKeyboardHints = true,
|
|
|
151
143
|
const [start, end] = newValue;
|
|
152
144
|
if (start && end && hasUnavailableDateInRange(start, end)) return;
|
|
153
145
|
}
|
|
154
|
-
|
|
146
|
+
onChange?.(newValue);
|
|
155
147
|
}, [
|
|
156
|
-
|
|
148
|
+
onChange,
|
|
157
149
|
disallowDisabledDatesInRange,
|
|
158
150
|
isRange,
|
|
159
151
|
hasUnavailableDateInRange
|
|
@@ -169,9 +161,7 @@ const Calendar = ({ type = 'single', showTime = false, showKeyboardHints = true,
|
|
|
169
161
|
isRange,
|
|
170
162
|
showKeyboardHints,
|
|
171
163
|
showTime,
|
|
172
|
-
readonly
|
|
173
|
-
timeRef,
|
|
174
|
-
commitValue
|
|
164
|
+
readonly
|
|
175
165
|
};
|
|
176
166
|
return /*#__PURE__*/ jsx(DatePicker.Root, {
|
|
177
167
|
selectionMode: isRange ? 'range' : 'single',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type ReactNode
|
|
2
|
-
import type { CalendarType
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { CalendarType } from './types';
|
|
3
3
|
export interface CalendarContextValue {
|
|
4
4
|
/** Calendar type: 'single' for one month, 'range' for two months (date range selection) */
|
|
5
5
|
type: CalendarType;
|
|
@@ -11,23 +11,6 @@ export interface CalendarContextValue {
|
|
|
11
11
|
showTime: boolean;
|
|
12
12
|
/** Whether calendar is readonly (display only, no selection) */
|
|
13
13
|
readonly: boolean;
|
|
14
|
-
/**
|
|
15
|
-
* Last-known time-of-day, owned by `Calendar`. In `showTime` mode the grid
|
|
16
|
-
* only produces date-only values; this ref carries the hour/minute so they
|
|
17
|
-
* survive a day change and so `Calendar`'s `onChange` can emit a full
|
|
18
|
-
* `CalendarDateTime`. Undefined outside a `Calendar`.
|
|
19
|
-
*/
|
|
20
|
-
timeRef?: RefObject<{
|
|
21
|
-
hour: number;
|
|
22
|
-
minute: number;
|
|
23
|
-
}>;
|
|
24
|
-
/**
|
|
25
|
-
* Push a value straight to the consumer `onChange`, bypassing Ark's machine.
|
|
26
|
-
* Ark's DatePicker dedupes its value by date (year/month/day) and silently
|
|
27
|
-
* drops a same-date, time-only change, so the `showTime` header uses this to
|
|
28
|
-
* commit a time edit. Undefined outside a `Calendar`.
|
|
29
|
-
*/
|
|
30
|
-
commitValue?: (value: DateValue[]) => void;
|
|
31
14
|
}
|
|
32
15
|
export interface CalendarProviderProps {
|
|
33
16
|
children: ReactNode;
|
|
@@ -12,7 +12,6 @@ import { Tooltip } from "../Tooltip/index.js";
|
|
|
12
12
|
import { TooltipContent } from "../Tooltip/TooltipContent.js";
|
|
13
13
|
import { TooltipTrigger } from "../Tooltip/TooltipTrigger.js";
|
|
14
14
|
import { useCalendarContext } from "./CalendarContext.js";
|
|
15
|
-
import { toDateValue } from "./dateValue.js";
|
|
16
15
|
const CalendarHeader = ({ showArrows = true, showJumpToToday = false, visibleRangeText: visibleRangeTextProp })=>{
|
|
17
16
|
const { isRange, readonly } = useCalendarContext();
|
|
18
17
|
const effectiveShowArrows = showArrows && !readonly;
|
|
@@ -61,7 +60,7 @@ const CalendarHeader = ({ showArrows = true, showJumpToToday = false, visibleRan
|
|
|
61
60
|
size: "small",
|
|
62
61
|
"aria-label": "Jump to today",
|
|
63
62
|
onClick: ()=>{
|
|
64
|
-
api.setFocusedValue(
|
|
63
|
+
api.setFocusedValue(todayDate);
|
|
65
64
|
},
|
|
66
65
|
children: /*#__PURE__*/ jsx(JumpToTodayIcon, {
|
|
67
66
|
size: "md"
|
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback,
|
|
2
|
+
import { useCallback, useState } from "react";
|
|
3
3
|
import { DatePicker } from "@ark-ui/react";
|
|
4
|
+
import { CalendarDateTime } from "@internationalized/date";
|
|
4
5
|
import { cn } from "../../utils/cn.js";
|
|
5
6
|
import { DateInput } from "../DateInput/index.js";
|
|
6
7
|
import { useCalendarContext } from "./CalendarContext.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const toReactAriaDateValue = (date)=>{
|
|
9
|
+
if (!date) return null;
|
|
10
|
+
return date;
|
|
11
|
+
};
|
|
12
|
+
const toArkDateValue = (date)=>{
|
|
13
|
+
if (!date) return;
|
|
14
|
+
return date;
|
|
15
|
+
};
|
|
16
|
+
const withTime = (value, time)=>{
|
|
17
|
+
if (!value) return null;
|
|
18
|
+
if ('hour' in value) return value;
|
|
19
|
+
return new CalendarDateTime(value.year, value.month, value.day, time.hour, time.minute);
|
|
20
|
+
};
|
|
21
|
+
const SingleDateInputInner = ({ api, readonly, showTime })=>{
|
|
22
|
+
const [time, setTime] = useState({
|
|
23
|
+
hour: 0,
|
|
24
|
+
minute: 0
|
|
25
|
+
});
|
|
10
26
|
const handleChange = useCallback((newValue)=>{
|
|
11
27
|
const arkValue = toArkDateValue(newValue);
|
|
12
28
|
if (arkValue) api.setValue([
|
|
@@ -17,34 +33,20 @@ const SingleDateInputInner = ({ api, readonly, showTime, timeRef, commitValue })
|
|
|
17
33
|
api
|
|
18
34
|
]);
|
|
19
35
|
const handleDateTimeChange = useCallback((newValue)=>{
|
|
20
|
-
if (newValue && 'hour' in newValue
|
|
36
|
+
if (newValue && 'hour' in newValue) setTime({
|
|
21
37
|
hour: newValue.hour,
|
|
22
38
|
minute: newValue.minute
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const arkValue = toArkDateValue(newValue);
|
|
26
|
-
if (arkValue) commitValue?.([
|
|
27
|
-
arkValue
|
|
28
|
-
]);
|
|
29
|
-
bumpTime();
|
|
30
|
-
} else handleChange(newValue);
|
|
39
|
+
});
|
|
40
|
+
handleChange(newValue);
|
|
31
41
|
}, [
|
|
32
|
-
|
|
33
|
-
handleChange,
|
|
34
|
-
timeRef,
|
|
35
|
-
commitValue
|
|
42
|
+
handleChange
|
|
36
43
|
]);
|
|
37
44
|
const inputValue = toReactAriaDateValue(api.value[0]);
|
|
38
|
-
const time = timeRef?.current ?? {
|
|
39
|
-
hour: 0,
|
|
40
|
-
minute: 0
|
|
41
|
-
};
|
|
42
|
-
const dateTimeValue = inputValue ? toReactAriaDateValue(withTime(inputValue, time)) : null;
|
|
43
45
|
return /*#__PURE__*/ jsx("div", {
|
|
44
46
|
className: "flex flex-1",
|
|
45
47
|
onKeyDown: (e)=>e.stopPropagation(),
|
|
46
48
|
children: showTime ? /*#__PURE__*/ jsx(DateInput, {
|
|
47
|
-
value:
|
|
49
|
+
value: withTime(inputValue, time),
|
|
48
50
|
onChange: handleDateTimeChange,
|
|
49
51
|
readOnly: readonly,
|
|
50
52
|
granularity: "minute",
|
|
@@ -119,7 +121,7 @@ const RangeDateInputInner = ({ api, readonly })=>{
|
|
|
119
121
|
});
|
|
120
122
|
};
|
|
121
123
|
const CalendarInputHeader = ({ className })=>{
|
|
122
|
-
const { isRange, readonly, showTime
|
|
124
|
+
const { isRange, readonly, showTime } = useCalendarContext();
|
|
123
125
|
return /*#__PURE__*/ jsx("div", {
|
|
124
126
|
className: cn('flex flex-1 items-center', 'pt-20 pb-4 px-20', className),
|
|
125
127
|
children: /*#__PURE__*/ jsx(DatePicker.Context, {
|
|
@@ -129,9 +131,7 @@ const CalendarInputHeader = ({ className })=>{
|
|
|
129
131
|
}) : /*#__PURE__*/ jsx(SingleDateInputInner, {
|
|
130
132
|
api: api,
|
|
131
133
|
readonly: readonly,
|
|
132
|
-
showTime: showTime
|
|
133
|
-
timeRef: timeRef,
|
|
134
|
-
commitValue: commitValue
|
|
134
|
+
showTime: showTime
|
|
135
135
|
})
|
|
136
136
|
})
|
|
137
137
|
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type FC, type ReactNode, type Ref } from 'react';
|
|
2
|
+
import { Slider as ArkSlider } from '@ark-ui/react/slider';
|
|
3
|
+
import { type TestableProps } from '../../utils/testId';
|
|
4
|
+
/** Ark/Zag machine-config props forwarded to the headless `Slider.Root`. */
|
|
5
|
+
type SliderRootConfig = Pick<ArkSlider.RootProps, 'value' | 'defaultValue' | 'min' | 'max' | 'step' | 'disabled' | 'readOnly' | 'getAriaValueText' | 'name' | 'form' | 'minStepsBetweenThumbs' | 'thumbCollisionBehavior'>;
|
|
6
|
+
export interface SliderProps extends SliderRootConfig, TestableProps {
|
|
7
|
+
/** Error state → maps to Ark `invalid` (repaints the handle; the message is the Field's job). */
|
|
8
|
+
error?: boolean;
|
|
9
|
+
className?: string;
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
/** Fires live during drag with the current value array (`[n]` single, `[low, high]` range). */
|
|
12
|
+
onValueChange?: (value: number[]) => void;
|
|
13
|
+
/** Fires once on drag release with the committed value array. */
|
|
14
|
+
onValueChangeEnd?: (value: number[]) => void;
|
|
15
|
+
/** Ref to the Ark Slider root element. */
|
|
16
|
+
ref?: Ref<HTMLDivElement>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Slider — pick an approximate, bounded value (or range) by dragging a handle along
|
|
20
|
+
* a track. Built on `@ark-ui/react/slider`.
|
|
21
|
+
*
|
|
22
|
+
* Compose the parts: `SliderControl` (renders the track + range) holds one
|
|
23
|
+
* `SliderThumb` per thumb (the real `role="slider"` node — consumer `data-*` /
|
|
24
|
+
* analytics / `ref` land there) and an optional `SliderMarks`. `SliderInput` and
|
|
25
|
+
* `SliderValue` are optional readouts. Single vs range is driven by the length of
|
|
26
|
+
* `value` / `defaultValue` — render one `SliderThumb` per entry. Thumbs never cross
|
|
27
|
+
* (`thumbCollisionBehavior` defaults to `'none'`); set `minStepsBetweenThumbs` for a gap.
|
|
28
|
+
*
|
|
29
|
+
* It reads `Field` context like `Input`/`Textarea`, so wrapping it in `<Field>` wires
|
|
30
|
+
* label / invalid / disabled. When an exact figure matters, pair with `NumberInput`;
|
|
31
|
+
* for 2–7 fixed options use `SegmentedControl`. See `docs/slider-design-spec.md`.
|
|
32
|
+
*/
|
|
33
|
+
export declare const Slider: FC<SliderProps>;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import code_connect from "@figma/code-connect";
|
|
3
|
+
import { Field, FieldLabel } from "../Field/index.js";
|
|
4
|
+
import { Slider } from "./Slider.js";
|
|
5
|
+
import { SliderControl } from "./SliderControl.js";
|
|
6
|
+
import { SliderInput } from "./SliderInput.js";
|
|
7
|
+
import { SliderMarks } from "./SliderMarks.js";
|
|
8
|
+
import { SliderThumb } from "./SliderThumb.js";
|
|
9
|
+
const SLIDER_URL = 'https://www.figma.com/design/VKb5gW46uSGw0rqrhZsbXT/WADS-Components?node-id=11354-181';
|
|
10
|
+
const SLIDER_INPUT_URL = 'https://www.figma.com/design/VKb5gW46uSGw0rqrhZsbXT/WADS-Components?node-id=11358-1807';
|
|
11
|
+
code_connect.connect(Slider, SLIDER_URL, {
|
|
12
|
+
example: ()=>/*#__PURE__*/ jsx(Slider, {
|
|
13
|
+
"aria-label": "Value",
|
|
14
|
+
defaultValue: [
|
|
15
|
+
50
|
|
16
|
+
],
|
|
17
|
+
children: /*#__PURE__*/ jsx(SliderControl, {
|
|
18
|
+
children: /*#__PURE__*/ jsx(SliderThumb, {
|
|
19
|
+
"aria-label": "Value"
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
});
|
|
24
|
+
code_connect.connect(Slider, SLIDER_URL, {
|
|
25
|
+
variant: {
|
|
26
|
+
Double: 'On'
|
|
27
|
+
},
|
|
28
|
+
example: ()=>/*#__PURE__*/ jsx(Slider, {
|
|
29
|
+
defaultValue: [
|
|
30
|
+
20,
|
|
31
|
+
80
|
|
32
|
+
],
|
|
33
|
+
children: /*#__PURE__*/ jsxs(SliderControl, {
|
|
34
|
+
children: [
|
|
35
|
+
/*#__PURE__*/ jsx(SliderThumb, {
|
|
36
|
+
index: 0
|
|
37
|
+
}),
|
|
38
|
+
/*#__PURE__*/ jsx(SliderThumb, {
|
|
39
|
+
index: 1
|
|
40
|
+
})
|
|
41
|
+
]
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
});
|
|
45
|
+
code_connect.connect(Slider, SLIDER_URL, {
|
|
46
|
+
variant: {
|
|
47
|
+
Ticks: 'On'
|
|
48
|
+
},
|
|
49
|
+
example: ()=>/*#__PURE__*/ jsx(Slider, {
|
|
50
|
+
"aria-label": "Value",
|
|
51
|
+
step: 25,
|
|
52
|
+
defaultValue: [
|
|
53
|
+
50
|
|
54
|
+
],
|
|
55
|
+
children: /*#__PURE__*/ jsxs(SliderControl, {
|
|
56
|
+
children: [
|
|
57
|
+
/*#__PURE__*/ jsx(SliderThumb, {
|
|
58
|
+
"aria-label": "Value"
|
|
59
|
+
}),
|
|
60
|
+
/*#__PURE__*/ jsx(SliderMarks, {
|
|
61
|
+
marks: [
|
|
62
|
+
{
|
|
63
|
+
value: 0,
|
|
64
|
+
label: '0'
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
value: 25,
|
|
68
|
+
label: '25'
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
value: 50,
|
|
72
|
+
label: '50'
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
value: 75,
|
|
76
|
+
label: '75'
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
value: 100,
|
|
80
|
+
label: '100'
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
})
|
|
84
|
+
]
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
});
|
|
88
|
+
code_connect.connect(Slider, SLIDER_URL, {
|
|
89
|
+
variant: {
|
|
90
|
+
Input: 'On'
|
|
91
|
+
},
|
|
92
|
+
example: ()=>/*#__PURE__*/ jsxs(Slider, {
|
|
93
|
+
"aria-label": "Value",
|
|
94
|
+
defaultValue: [
|
|
95
|
+
50
|
|
96
|
+
],
|
|
97
|
+
children: [
|
|
98
|
+
/*#__PURE__*/ jsx(SliderControl, {
|
|
99
|
+
children: /*#__PURE__*/ jsx(SliderThumb, {
|
|
100
|
+
"aria-label": "Value"
|
|
101
|
+
})
|
|
102
|
+
}),
|
|
103
|
+
/*#__PURE__*/ jsx(SliderInput, {})
|
|
104
|
+
]
|
|
105
|
+
})
|
|
106
|
+
});
|
|
107
|
+
code_connect.connect(Slider, SLIDER_INPUT_URL, {
|
|
108
|
+
example: ()=>/*#__PURE__*/ jsxs(Field, {
|
|
109
|
+
children: [
|
|
110
|
+
/*#__PURE__*/ jsx(FieldLabel, {
|
|
111
|
+
children: "Risk threshold"
|
|
112
|
+
}),
|
|
113
|
+
/*#__PURE__*/ jsx(Slider, {
|
|
114
|
+
defaultValue: [
|
|
115
|
+
50
|
|
116
|
+
],
|
|
117
|
+
children: /*#__PURE__*/ jsx(SliderControl, {
|
|
118
|
+
children: /*#__PURE__*/ jsx(SliderThumb, {})
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
]
|
|
122
|
+
})
|
|
123
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useMemo, useState } from "react";
|
|
3
|
+
import { useFieldContext } from "@ark-ui/react/field";
|
|
4
|
+
import { Slider } from "@ark-ui/react/slider";
|
|
5
|
+
import { cn } from "../../utils/cn.js";
|
|
6
|
+
import { TestIdProvider } from "../../utils/testId.js";
|
|
7
|
+
import { sliderVariants } from "./classes.js";
|
|
8
|
+
import { SliderRootContextProvider } from "./SliderContext.js";
|
|
9
|
+
const sameMarks = (a, b)=>a.length === b.length && a.every((mark, i)=>mark.value === b[i]?.value && mark.label === b[i]?.label);
|
|
10
|
+
const Slider_Slider = ({ value, defaultValue = [
|
|
11
|
+
50
|
|
12
|
+
], min, max, step, disabled, readOnly, getAriaValueText, name, form, minStepsBetweenThumbs, thumbCollisionBehavior, error = false, className, children, onValueChange, onValueChangeEnd, ref, 'data-testid': testId })=>{
|
|
13
|
+
const field = useFieldContext();
|
|
14
|
+
const invalid = error || Boolean(field?.invalid);
|
|
15
|
+
const isDisabled = disabled ?? field?.disabled ?? false;
|
|
16
|
+
const isReadOnly = readOnly ?? field?.readOnly ?? false;
|
|
17
|
+
const thumbCount = (value ?? defaultValue).length;
|
|
18
|
+
const isRange = thumbCount > 1;
|
|
19
|
+
const [marks, setMarks] = useState([]);
|
|
20
|
+
const registerMarks = useCallback((next)=>{
|
|
21
|
+
setMarks((prev)=>sameMarks(prev, next) ? prev : next);
|
|
22
|
+
}, []);
|
|
23
|
+
const resolvedGetAriaValueText = useMemo(()=>getAriaValueText ?? (({ value: v })=>marks.find((mark)=>mark.value === v)?.label ?? String(v)), [
|
|
24
|
+
getAriaValueText,
|
|
25
|
+
marks
|
|
26
|
+
]);
|
|
27
|
+
const fieldLabelId = field && !isRange ? field.ids.label : void 0;
|
|
28
|
+
const handleValueChange = useCallback((details)=>onValueChange?.(details.value), [
|
|
29
|
+
onValueChange
|
|
30
|
+
]);
|
|
31
|
+
const handleValueChangeEnd = useCallback((details)=>onValueChangeEnd?.(details.value), [
|
|
32
|
+
onValueChangeEnd
|
|
33
|
+
]);
|
|
34
|
+
const contextValue = useMemo(()=>({
|
|
35
|
+
isRange,
|
|
36
|
+
thumbCount,
|
|
37
|
+
invalid,
|
|
38
|
+
disabled: isDisabled,
|
|
39
|
+
readOnly: isReadOnly,
|
|
40
|
+
ariaDescribedby: field?.ariaDescribedby,
|
|
41
|
+
fieldLabelId,
|
|
42
|
+
marks,
|
|
43
|
+
registerMarks
|
|
44
|
+
}), [
|
|
45
|
+
isRange,
|
|
46
|
+
thumbCount,
|
|
47
|
+
invalid,
|
|
48
|
+
isDisabled,
|
|
49
|
+
isReadOnly,
|
|
50
|
+
field?.ariaDescribedby,
|
|
51
|
+
fieldLabelId,
|
|
52
|
+
marks,
|
|
53
|
+
registerMarks
|
|
54
|
+
]);
|
|
55
|
+
return /*#__PURE__*/ jsx(Slider.Root, {
|
|
56
|
+
ref: ref,
|
|
57
|
+
value: value,
|
|
58
|
+
defaultValue: defaultValue,
|
|
59
|
+
min: min,
|
|
60
|
+
max: max,
|
|
61
|
+
step: step,
|
|
62
|
+
disabled: isDisabled,
|
|
63
|
+
readOnly: isReadOnly,
|
|
64
|
+
invalid: invalid,
|
|
65
|
+
getAriaValueText: resolvedGetAriaValueText,
|
|
66
|
+
name: name,
|
|
67
|
+
form: form,
|
|
68
|
+
minStepsBetweenThumbs: minStepsBetweenThumbs,
|
|
69
|
+
thumbCollisionBehavior: thumbCollisionBehavior,
|
|
70
|
+
onValueChange: onValueChange ? handleValueChange : void 0,
|
|
71
|
+
onValueChangeEnd: onValueChangeEnd ? handleValueChangeEnd : void 0,
|
|
72
|
+
"data-slot": "slider",
|
|
73
|
+
"data-testid": testId,
|
|
74
|
+
className: cn(sliderVariants(), className),
|
|
75
|
+
children: /*#__PURE__*/ jsx(SliderRootContextProvider, {
|
|
76
|
+
value: contextValue,
|
|
77
|
+
children: /*#__PURE__*/ jsx(TestIdProvider, {
|
|
78
|
+
value: testId,
|
|
79
|
+
children: children
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
Slider_Slider.displayName = 'Slider';
|
|
85
|
+
export { Slider_Slider as Slider };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { SliderMark } from './types';
|
|
2
|
+
export interface SliderRootContextValue {
|
|
3
|
+
/** True when the value has 2+ entries (range) — drives slot-naming + label defaults. */
|
|
4
|
+
isRange: boolean;
|
|
5
|
+
/** Expected thumb count (`value`/`defaultValue` length) — for the dev thumb-count guard. */
|
|
6
|
+
thumbCount: number;
|
|
7
|
+
/** Resolved invalid state (own `error` || Field `invalid`) — for sub-part error styling. */
|
|
8
|
+
invalid: boolean;
|
|
9
|
+
/** Resolved disabled state (own `disabled` ?? Field `disabled`). */
|
|
10
|
+
disabled: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Resolved read-only state (own `readOnly` ?? Field `readOnly`). Ark's thumb guards
|
|
13
|
+
* drag/keyboard on this, but its `setThumbValue` does NOT — so parts that write
|
|
14
|
+
* imperatively (`SliderInput`, `SliderMarks` click-to-jump) must honour it themselves.
|
|
15
|
+
*/
|
|
16
|
+
readOnly: boolean;
|
|
17
|
+
/** Field help/error id to announce on the thumb (`aria-describedby`). */
|
|
18
|
+
ariaDescribedby?: string;
|
|
19
|
+
/** Field label id — fallback accessible name for a single thumb with no explicit label. */
|
|
20
|
+
fieldLabelId?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Marks published by `<SliderMarks>`, mirrored into root state so `getAriaValueText`
|
|
23
|
+
* (and `SliderValue` / the thumb tooltip) resolve ordinal labels. Empty when there
|
|
24
|
+
* are no marks.
|
|
25
|
+
*/
|
|
26
|
+
marks: SliderMark[];
|
|
27
|
+
/** Called by `<SliderMarks>` to publish its marks to the root (no-op if unchanged). */
|
|
28
|
+
registerMarks: (marks: SliderMark[]) => void;
|
|
29
|
+
}
|
|
30
|
+
export declare const SliderRootContextProvider: import("react").Provider<SliderRootContextValue | null>;
|
|
31
|
+
export declare const useSliderRootContext: () => SliderRootContextValue;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
const SliderRootContext = /*#__PURE__*/ createContext(null);
|
|
3
|
+
const SliderRootContextProvider = SliderRootContext.Provider;
|
|
4
|
+
const useSliderRootContext = ()=>{
|
|
5
|
+
const context = useContext(SliderRootContext);
|
|
6
|
+
if (!context) throw new Error('Slider sub-components must be rendered inside <Slider>.');
|
|
7
|
+
return context;
|
|
8
|
+
};
|
|
9
|
+
export { SliderRootContextProvider, useSliderRootContext };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type FC, type ReactNode, type Ref } from 'react';
|
|
2
|
+
export interface SliderControlProps {
|
|
3
|
+
children?: ReactNode;
|
|
4
|
+
className?: string;
|
|
5
|
+
/** Ref to the Ark Control element (the track region). */
|
|
6
|
+
ref?: Ref<HTMLDivElement>;
|
|
7
|
+
}
|
|
8
|
+
/** The interactive track region — renders the track + range, then the thumb(s) / marks. */
|
|
9
|
+
export declare const SliderControl: FC<SliderControlProps>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Children, isValidElement } from "react";
|
|
3
|
+
import { Slider } from "@ark-ui/react/slider";
|
|
4
|
+
import { cn } from "../../utils/cn.js";
|
|
5
|
+
import { useTestId } from "../../utils/testId.js";
|
|
6
|
+
import { sliderControlClassNames, sliderRangeClassNames, sliderTrackClassNames } from "./classes.js";
|
|
7
|
+
import { useSliderRootContext } from "./SliderContext.js";
|
|
8
|
+
import { SliderThumb } from "./SliderThumb.js";
|
|
9
|
+
const SliderControl = ({ children, className, ref })=>{
|
|
10
|
+
const { thumbCount } = useSliderRootContext();
|
|
11
|
+
const testId = useTestId('control');
|
|
12
|
+
if ('production' !== process.env.NODE_ENV) {
|
|
13
|
+
const renderedThumbs = Children.toArray(children).filter((child)=>/*#__PURE__*/ isValidElement(child) && child.type === SliderThumb).length;
|
|
14
|
+
if (renderedThumbs > 0 && renderedThumbs !== thumbCount) console.warn(`[Slider] Rendered ${renderedThumbs} <SliderThumb> but the value has ${thumbCount} entr${1 === thumbCount ? 'y' : 'ies'}. Render exactly one <SliderThumb index> per value entry.`);
|
|
15
|
+
}
|
|
16
|
+
return /*#__PURE__*/ jsxs(Slider.Control, {
|
|
17
|
+
ref: ref,
|
|
18
|
+
"data-slot": "slider-control",
|
|
19
|
+
"data-testid": testId,
|
|
20
|
+
className: cn(sliderControlClassNames, className),
|
|
21
|
+
children: [
|
|
22
|
+
/*#__PURE__*/ jsx(Slider.Track, {
|
|
23
|
+
"data-slot": "slider-track",
|
|
24
|
+
className: sliderTrackClassNames,
|
|
25
|
+
children: /*#__PURE__*/ jsx(Slider.Range, {
|
|
26
|
+
"data-slot": "slider-range",
|
|
27
|
+
className: sliderRangeClassNames
|
|
28
|
+
})
|
|
29
|
+
}),
|
|
30
|
+
children
|
|
31
|
+
]
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
SliderControl.displayName = 'SliderControl';
|
|
35
|
+
export { SliderControl };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type FC, type InputHTMLAttributes, type Ref } from 'react';
|
|
2
|
+
export interface SliderInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'value' | 'defaultValue'> {
|
|
3
|
+
/** Which thumb this box is bound to (0 = single/low, 1 = high). */
|
|
4
|
+
index?: number;
|
|
5
|
+
/** Ref to the real `<input>` node (analytics / focus management). */
|
|
6
|
+
ref?: Ref<HTMLInputElement>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Inline numbers-only `Input` two-way bound to the thumb at `index`: a plain box (no
|
|
10
|
+
* stepper, fixed width — the DS `Input`, matching Figma `_text-box`). The slider machine
|
|
11
|
+
* owns clamp/snap/no-cross, so the box needs no bounds of its own.
|
|
12
|
+
*
|
|
13
|
+
* Editing uses a local draft buffer: while focused, the box shows exactly what the user
|
|
14
|
+
* typed (so an empty field, a leading `-`, a trailing `.`, or a value that would snap/clamp
|
|
15
|
+
* survive the keystroke) and commits to `setThumbValue` on blur / Enter — then drops the
|
|
16
|
+
* draft so the box re-syncs to the machine's clamped + snapped value. Binding `value`
|
|
17
|
+
* straight to the snapped machine value instead would fight every intermediate keystroke.
|
|
18
|
+
*
|
|
19
|
+
* It is the real interactive node, so arbitrary consumer `data-*` / `aria-*` / `id` / `ref`
|
|
20
|
+
* (canonically `data-analytics-id` / `data-analytics-props`) forward straight to the
|
|
21
|
+
* `<input>` (see `docs/metrics/contract.md`).
|
|
22
|
+
*
|
|
23
|
+
* Explicit `useId` default so two range boxes never collide on the wrapping field's control
|
|
24
|
+
* id (and `FieldLabel`'s `htmlFor` keeps naming the thumb, not a box); a consumer `id` wins.
|
|
25
|
+
*/
|
|
26
|
+
export declare const SliderInput: FC<SliderInputProps>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useId, useState } from "react";
|
|
3
|
+
import { useSliderContext } from "@ark-ui/react/slider";
|
|
4
|
+
import { cn } from "../../utils/cn.js";
|
|
5
|
+
import { useTestId } from "../../utils/testId.js";
|
|
6
|
+
import { Input } from "../Input/index.js";
|
|
7
|
+
import { sliderInputBoxClassNames } from "./classes.js";
|
|
8
|
+
import { useSliderRootContext } from "./SliderContext.js";
|
|
9
|
+
const SliderInput = ({ index = 0, 'aria-label': ariaLabel, id: idProp, className, ref, onChange: onChangeProp, onBlur: onBlurProp, onKeyDown: onKeyDownProp, ...rest })=>{
|
|
10
|
+
const { isRange, disabled, invalid, readOnly } = useSliderRootContext();
|
|
11
|
+
const api = useSliderContext();
|
|
12
|
+
const autoId = useId();
|
|
13
|
+
const id = idProp ?? autoId;
|
|
14
|
+
const testId = useTestId(isRange ? `input-${index}` : 'input');
|
|
15
|
+
const defaultLabel = isRange ? 0 === index ? 'Minimum value' : 'Maximum value' : 'Value';
|
|
16
|
+
const machineText = String(api.value[index] ?? '');
|
|
17
|
+
const [draft, setDraft] = useState(null);
|
|
18
|
+
const value = draft ?? machineText;
|
|
19
|
+
const commit = useCallback(()=>{
|
|
20
|
+
if (null === draft) return;
|
|
21
|
+
const next = Number(draft);
|
|
22
|
+
if ('' !== draft && !Number.isNaN(next)) api.setThumbValue(index, next);
|
|
23
|
+
setDraft(null);
|
|
24
|
+
}, [
|
|
25
|
+
api,
|
|
26
|
+
draft,
|
|
27
|
+
index
|
|
28
|
+
]);
|
|
29
|
+
const handleChange = useCallback((event)=>{
|
|
30
|
+
onChangeProp?.(event);
|
|
31
|
+
if (readOnly) return;
|
|
32
|
+
setDraft(event.target.value);
|
|
33
|
+
}, [
|
|
34
|
+
onChangeProp,
|
|
35
|
+
readOnly
|
|
36
|
+
]);
|
|
37
|
+
const handleBlur = useCallback((event)=>{
|
|
38
|
+
onBlurProp?.(event);
|
|
39
|
+
commit();
|
|
40
|
+
}, [
|
|
41
|
+
onBlurProp,
|
|
42
|
+
commit
|
|
43
|
+
]);
|
|
44
|
+
const handleKeyDown = useCallback((event)=>{
|
|
45
|
+
onKeyDownProp?.(event);
|
|
46
|
+
if ('Enter' === event.key) commit();
|
|
47
|
+
}, [
|
|
48
|
+
onKeyDownProp,
|
|
49
|
+
commit
|
|
50
|
+
]);
|
|
51
|
+
return /*#__PURE__*/ jsx(Input, {
|
|
52
|
+
...rest,
|
|
53
|
+
ref: ref,
|
|
54
|
+
inputMode: "numeric",
|
|
55
|
+
id: id,
|
|
56
|
+
value: value,
|
|
57
|
+
disabled: disabled,
|
|
58
|
+
readOnly: readOnly,
|
|
59
|
+
error: invalid,
|
|
60
|
+
"aria-label": ariaLabel ?? defaultLabel,
|
|
61
|
+
"data-testid": testId,
|
|
62
|
+
className: cn(sliderInputBoxClassNames, className),
|
|
63
|
+
onChange: handleChange,
|
|
64
|
+
onBlur: handleBlur,
|
|
65
|
+
onKeyDown: handleKeyDown
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
SliderInput.displayName = 'SliderInput';
|
|
69
|
+
export { SliderInput };
|