@uniai-fe/uds-primitives 0.0.18 → 0.0.20

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.
Files changed (49) hide show
  1. package/README.md +9 -5
  2. package/dist/styles.css +55 -1017
  3. package/package.json +1 -6
  4. package/src/components/alternate/styles/index.scss +0 -2
  5. package/src/components/badge/styles/index.scss +1 -3
  6. package/src/components/button/styles/button.scss +0 -1
  7. package/src/components/button/styles/round-button.scss +0 -2
  8. package/src/components/button/styles/text-button.scss +0 -2
  9. package/src/components/calendar/styles/index.scss +0 -2
  10. package/src/components/checkbox/styles/index.scss +1 -3
  11. package/src/components/chip/styles/index.scss +1 -3
  12. package/src/components/drawer/styles/index.scss +1 -3
  13. package/src/components/dropdown/styles/index.scss +0 -2
  14. package/src/components/input/styles/index.scss +2 -3
  15. package/src/components/label/styles/index.scss +0 -2
  16. package/src/components/navigation/styles/index.scss +1 -3
  17. package/src/components/pagination/styles/index.scss +0 -2
  18. package/src/components/radio/styles/index.scss +1 -3
  19. package/src/components/scrollbar/styles/index.scss +0 -2
  20. package/src/components/segmented-control/markup/Container.tsx +255 -0
  21. package/src/components/segmented-control/markup/Indicator.tsx +31 -0
  22. package/src/components/segmented-control/markup/List.tsx +125 -0
  23. package/src/components/segmented-control/markup/index.ts +1 -1
  24. package/src/components/segmented-control/styles/index.scss +51 -50
  25. package/src/components/segmented-control/types/index.ts +48 -10
  26. package/src/components/select/styles/index.scss +0 -2
  27. package/src/components/spinner/styles/index.scss +0 -2
  28. package/src/components/tab/styles/index.scss +0 -2
  29. package/src/components/table/styles/index.scss +0 -2
  30. package/src/index.scss +2 -4
  31. package/src/index.tsx +1 -2
  32. package/src/components/dialog/hooks/index.ts +0 -4
  33. package/src/components/dialog/img/.gitkeep +0 -0
  34. package/src/components/dialog/index.scss +0 -1
  35. package/src/components/dialog/index.tsx +0 -6
  36. package/src/components/dialog/markup/ConfirmDialog.tsx +0 -339
  37. package/src/components/dialog/markup/NoticeDialog.tsx +0 -209
  38. package/src/components/dialog/markup/index.tsx +0 -4
  39. package/src/components/dialog/styles/base.scss +0 -153
  40. package/src/components/dialog/styles/confirm.scss +0 -58
  41. package/src/components/dialog/styles/index.scss +0 -3
  42. package/src/components/dialog/styles/notice.scss +0 -65
  43. package/src/components/dialog/types/index.ts +0 -113
  44. package/src/components/dialog/utils/index.ts +0 -4
  45. package/src/components/segmented-control/markup/SegmentedControl.tsx +0 -129
  46. package/src/theme/ThemeProvider.tsx +0 -25
  47. package/src/theme/config.ts +0 -29
  48. package/src/theme/index.ts +0 -3
  49. package/src/theme/overrides.scss +0 -215
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniai-fe/uds-primitives",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
4
4
  "description": "UNIAI Design System; Primitives Components Package",
5
5
  "type": "module",
6
6
  "private": false,
@@ -55,8 +55,6 @@
55
55
  "peerDependencies": {
56
56
  "@uniai-fe/uds-foundation": "^0.0.1",
57
57
  "@uniai-fe/util-functions": "^0.2.3",
58
- "@radix-ui/react-alert-dialog": "^1.1.15",
59
- "@radix-ui/react-dialog": "^1.1.15",
60
58
  "@radix-ui/react-visually-hidden": "^1.2.4",
61
59
  "react": ">= 19",
62
60
  "react-dom": ">= 19",
@@ -72,13 +70,10 @@
72
70
  "@radix-ui/react-primitive": "^2.1.4",
73
71
  "@radix-ui/react-radio-group": "^1.3.8",
74
72
  "@radix-ui/react-slot": "^1.2.4",
75
- "@radix-ui/themes": "^3.2.1",
76
73
  "clsx": "^2.1.1",
77
74
  "dayjs": "^1.11.19"
78
75
  },
79
76
  "devDependencies": {
80
- "@radix-ui/react-alert-dialog": "^1.1.15",
81
- "@radix-ui/react-dialog": "^1.1.15",
82
77
  "@radix-ui/react-visually-hidden": "^1.2.4",
83
78
  "@svgr/webpack": "^8.1.0",
84
79
  "@types/node": "^24.10.2",
@@ -1,3 +1 @@
1
- @use "@uniai-fe/uds-foundation/css";
2
-
3
1
  /* TODO(alternate): 스타일을 SOT 토큰 값으로 정의한다. */
@@ -1,7 +1,5 @@
1
- @use "@uniai-fe/uds-foundation/css";
2
-
3
1
  /* Badge 토큰을 foundation 변수에 맞춰 한 번 더 래핑해 override 여지를 둔다. */
4
- :where(.radix-themes, .theme-root, :root) {
2
+ .uds-theme-root {
5
3
  --theme-badge-height-xsmall: var(--theme-size-small-1, 20px);
6
4
  --theme-badge-height-small: var(--theme-size-small-2, 24px);
7
5
  --theme-badge-padding-inline-xsmall: var(--spacing-padding-3, 6px);
@@ -1,4 +1,3 @@
1
- @use "@uniai-fe/uds-foundation/css";
2
1
  @use "sass:map";
3
2
 
4
3
  :root {
@@ -1,5 +1,3 @@
1
- @use "@uniai-fe/uds-foundation/css";
2
-
3
1
  .button.button-template-round {
4
2
  min-width: auto;
5
3
  padding-block: var(
@@ -1,5 +1,3 @@
1
- @use "@uniai-fe/uds-foundation/css";
2
-
3
1
  .button.button-template-text {
4
2
  min-width: auto;
5
3
  border-color: transparent;
@@ -1,3 +1 @@
1
- @use "@uniai-fe/uds-foundation/css";
2
-
3
1
  /* TODO(calendar): 스타일을 SOT 토큰 값으로 정의한다. */
@@ -1,6 +1,4 @@
1
- @use "@uniai-fe/uds-foundation/css";
2
-
3
- :where(.radix-themes, .theme-root, :root) {
1
+ .uds-theme-root {
4
2
  --theme-checkbox-frame-size-medium: 20px;
5
3
  --theme-checkbox-frame-size-large: 24px;
6
4
  --theme-checkbox-indicator-size-medium: 16px;
@@ -1,7 +1,5 @@
1
- @use "@uniai-fe/uds-foundation/css";
2
-
3
1
  /* Chip 기본 토큰 래핑 */
4
- :where(.radix-themes, .theme-root, :root) {
2
+ .uds-theme-root {
5
3
  --theme-chip-height: var(--theme-size-small-3, 32px);
6
4
  --theme-chip-padding-inline: var(--spacing-padding-5, 12px);
7
5
  --theme-chip-radius: var(--theme-radius-medium-3, 8px);
@@ -1,6 +1,4 @@
1
- @use "@uniai-fe/uds-foundation/css";
2
-
3
- :where(.radix-themes, .theme-root, :root) {
1
+ .uds-theme-root {
4
2
  // Figma 기준 bottom sheet 여백/톤을 전역 변수로 정리한다.
5
3
  --drawer-overlay-bg: rgba(0, 0, 0, 0.44);
6
4
  --drawer-surface-bg: var(--color-bg-surface-static-white);
@@ -1,3 +1 @@
1
- @use "@uniai-fe/uds-foundation/css";
2
-
3
1
  /* TODO(dropdown): 스타일을 SOT 토큰 값으로 정의한다. */
@@ -1,6 +1,4 @@
1
- @use "@uniai-fe/uds-foundation/css";
2
-
3
- :where(.radix-themes, .theme-root, :root) {
1
+ .uds-theme-root {
4
2
  --theme-input-height-small: var(--theme-size-medium-1);
5
3
  --theme-input-height-medium: var(--theme-size-medium-2);
6
4
  --theme-input-height-large: var(--theme-size-medium-3);
@@ -196,6 +194,7 @@
196
194
  border: none;
197
195
  background: transparent;
198
196
  color: var(--theme-input-text-color);
197
+ caret-color: var(--theme-input-text-color);
199
198
  font-size: var(--font-body-medium-size);
200
199
  line-height: var(--font-body-medium-line-height);
201
200
  font-weight: var(--font-body-medium-weight);
@@ -1,3 +1 @@
1
- @use "@uniai-fe/uds-foundation/css";
2
-
3
1
  /* TODO(label): 스타일을 SOT 토큰 값으로 정의한다. */
@@ -1,6 +1,4 @@
1
- @use "@uniai-fe/uds-foundation/css";
2
-
3
- :where(.radix-themes, .theme-root, :root) {
1
+ .uds-theme-root {
4
2
  --theme-navigation-height: 86px;
5
3
  --theme-navigation-padding-inline: 32px;
6
4
  --theme-navigation-padding-block-start: 8px;
@@ -1,5 +1,3 @@
1
- @use "@uniai-fe/uds-foundation/css";
2
-
3
1
  /* Pagination native 구현 스타일 */
4
2
 
5
3
  .pagination {
@@ -1,6 +1,4 @@
1
- @use "@uniai-fe/uds-foundation/css";
2
-
3
- :where(.radix-themes, .theme-root) {
1
+ .uds-theme-root {
4
2
  --theme-radio-frame-size-medium: 20px;
5
3
  --theme-radio-frame-size-large: 24px;
6
4
  --theme-radio-indicator-size-medium: 16px;
@@ -1,3 +1 @@
1
- @use "@uniai-fe/uds-foundation/css";
2
-
3
1
  /* TODO(scrollbar): 스타일을 SOT 토큰 값으로 정의한다. */
@@ -0,0 +1,255 @@
1
+ import clsx from "clsx";
2
+ import {
3
+ forwardRef,
4
+ useCallback,
5
+ useEffect,
6
+ useLayoutEffect,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ type KeyboardEvent,
11
+ } from "react";
12
+ import type {
13
+ SegmentedControlIndicatorRect,
14
+ SegmentedControlProps,
15
+ SegmentedControlValue,
16
+ } from "../types";
17
+ import { SegmentedControlIndicator } from "./Indicator";
18
+ import { SegmentedControlList } from "./List";
19
+
20
+ // 빈 문자열이나 undefined를 내부 상태에서 공통적으로 undefined로 처리해 keepSelected 로직을 단순화한다.
21
+ const toNullableValue = (value?: SegmentedControlValue) =>
22
+ value === undefined || value === "" ? undefined : value;
23
+
24
+ /**
25
+ * @component SegmentedControl
26
+ * @desc keepSelected 토글과 indicator/list 레이어를 native로 구성한 Segmented Control 루트 컴포넌트.
27
+ * @param {SegmentedControlProps} props 루트에 전달되는 props.
28
+ * @param {SegmentedControlOption[]} props.options 선택지 배열.
29
+ * @param {string} props.ariaLabel 라디오 그룹 접근성 라벨.
30
+ * @param {boolean} [props.keepSelected=true] 이미 on 상태인 항목을 다시 눌러도 선택 해제를 막을지 여부.
31
+ * @param {SegmentedControlValue} [props.value] 제어형 값.
32
+ * @param {SegmentedControlValue} [props.defaultValue] 비제어 초기 값.
33
+ * @param {(value: SegmentedControlValue) => void} [props.onValueChange] 값 변경 콜백.
34
+ * @param {string} [props.className] 최상위 className.
35
+ */
36
+ const SegmentedControl = forwardRef<HTMLDivElement, SegmentedControlProps>(
37
+ (
38
+ {
39
+ options,
40
+ ariaLabel,
41
+ keepSelected = true,
42
+ className,
43
+ onValueChange,
44
+ value: valueProp,
45
+ defaultValue,
46
+ ...restProps
47
+ },
48
+ forwardedRef,
49
+ ) => {
50
+ // value prop 제공 여부로 제어형/비제어형을 구분한다.
51
+ const isControlled = valueProp !== undefined;
52
+ // keepSelected=false일 때 해제를 허용하기 위해 내부 값은 undefined 허용으로 둔다.
53
+ const [uncontrolledValue, setUncontrolledValue] = useState<
54
+ SegmentedControlValue | undefined
55
+ >(toNullableValue(defaultValue));
56
+ const selectedValue = toNullableValue(
57
+ isControlled ? valueProp : uncontrolledValue,
58
+ );
59
+ // 루트 컨테이너 ref는 indicator 측정과 ResizeObserver 구독에 활용된다.
60
+ const rootRef = useRef<HTMLDivElement | null>(null);
61
+ // 각 버튼 ref 배열. Arrow/Home/End 탐색 시 focus를 직접 이동한다.
62
+ const itemRefs = useRef<Array<HTMLButtonElement | null>>([]);
63
+ // indicatorRect는 indicator figure의 width/translateX를 계산하는 단일 소스다.
64
+ const [indicatorRect, setIndicatorRect] =
65
+ useState<SegmentedControlIndicatorRect>({
66
+ width: 0,
67
+ left: 0,
68
+ });
69
+
70
+ const emitChange = useCallback(
71
+ (nextValue: SegmentedControlValue | undefined) => {
72
+ if (!isControlled) {
73
+ setUncontrolledValue(nextValue);
74
+ }
75
+ onValueChange?.(nextValue ?? "");
76
+ },
77
+ [isControlled, onValueChange],
78
+ );
79
+
80
+ // 제어형/비제어형을 통합해 현재 선택된 value를 계산한다.
81
+ const resolvedValue = useMemo(
82
+ () => selectedValue ?? undefined,
83
+ [selectedValue],
84
+ );
85
+
86
+ // 현재 value에 해당하는 index. 값이 없으면 첫 번째 활성 항목을 포커스 대상으로 사용한다.
87
+ const selectedIndex = useMemo(
88
+ () => options.findIndex(option => option.value === selectedValue),
89
+ [options, selectedValue],
90
+ );
91
+ const fallbackIndex = useMemo(() => {
92
+ return options.findIndex(option => !option.disabled);
93
+ }, [options]);
94
+ const focusableIndex =
95
+ selectedIndex >= 0
96
+ ? selectedIndex
97
+ : fallbackIndex >= 0
98
+ ? fallbackIndex
99
+ : -1;
100
+
101
+ // Arrow 키 이동 시 다음 사용 가능한 index를 찾는다. disabled 옵션은 자동으로 건너뛴다.
102
+ const getNextEnabledIndex = useCallback(
103
+ (currentIndex: number, direction: 1 | -1) => {
104
+ if (options.length === 0) {
105
+ return -1;
106
+ }
107
+ let index = currentIndex;
108
+ for (let i = 0; i < options.length; i += 1) {
109
+ index = (index + direction + options.length) % options.length;
110
+ const option = options[index];
111
+ if (option && !option.disabled) {
112
+ return index;
113
+ }
114
+ }
115
+ return currentIndex;
116
+ },
117
+ [options],
118
+ );
119
+
120
+ // Button ref를 통해 포커스를 직접 이동한다.
121
+ const focusItemAt = useCallback((index: number) => {
122
+ const node = itemRefs.current[index];
123
+ if (node) {
124
+ node.focus();
125
+ }
126
+ }, []);
127
+
128
+ const handleArrowNavigation = useCallback(
129
+ (event: KeyboardEvent<HTMLButtonElement>, currentIndex: number) => {
130
+ if (options.length === 0) {
131
+ return;
132
+ }
133
+ const key = event.key;
134
+ if (
135
+ key !== "ArrowRight" &&
136
+ key !== "ArrowLeft" &&
137
+ key !== "ArrowUp" &&
138
+ key !== "ArrowDown"
139
+ ) {
140
+ return;
141
+ }
142
+ event.preventDefault();
143
+ const direction = key === "ArrowRight" || key === "ArrowDown" ? 1 : -1;
144
+ const nextIndex = getNextEnabledIndex(currentIndex, direction);
145
+ if (nextIndex === currentIndex) {
146
+ return;
147
+ }
148
+ const nextOption = options[nextIndex];
149
+ if (nextOption) {
150
+ focusItemAt(nextIndex);
151
+ emitChange(nextOption.value);
152
+ }
153
+ },
154
+ [emitChange, focusItemAt, getNextEnabledIndex, options],
155
+ );
156
+
157
+ /**
158
+ * 선택된 버튼의 위치 + root padding을 사용해 indicator 좌표를 계산한다.
159
+ * transform 이동만 수행하므로 padding 값을 더해 container 내부에 고정한다.
160
+ */
161
+ const measureIndicator = useCallback(() => {
162
+ if (!resolvedValue) {
163
+ setIndicatorRect({ width: 0, left: 0 });
164
+ return;
165
+ }
166
+ const index = options.findIndex(option => option.value === resolvedValue);
167
+ if (index === -1) {
168
+ setIndicatorRect({ width: 0, left: 0 });
169
+ return;
170
+ }
171
+ const node = itemRefs.current[index];
172
+ if (!node) {
173
+ return;
174
+ }
175
+ const paddingLeft =
176
+ typeof window !== "undefined" && rootRef.current
177
+ ? Number.parseFloat(
178
+ window.getComputedStyle(rootRef.current).paddingLeft ?? "0",
179
+ )
180
+ : 0;
181
+ const normalizedLeft = node.offsetLeft + paddingLeft;
182
+ setIndicatorRect({
183
+ width: node.offsetWidth,
184
+ left: Number.isNaN(normalizedLeft) ? 0 : normalizedLeft,
185
+ });
186
+ }, [options, resolvedValue]);
187
+
188
+ // DOM이 렌더된 직후 선택된 항목 기준으로 indicator를 한 번 맞춘다.
189
+ useLayoutEffect(() => {
190
+ measureIndicator();
191
+ }, [measureIndicator]);
192
+
193
+ useEffect(() => {
194
+ if (!rootRef.current || typeof ResizeObserver === "undefined") {
195
+ return;
196
+ }
197
+ // root padding이나 label 길이에 따른 폭 변경을 추적해 indicator 위치를 재계산한다.
198
+ const observer = new ResizeObserver(() => {
199
+ measureIndicator();
200
+ });
201
+ observer.observe(rootRef.current);
202
+ return () => observer.disconnect();
203
+ }, [measureIndicator]);
204
+
205
+ const setRootRef = useCallback(
206
+ (node: HTMLDivElement | null) => {
207
+ rootRef.current = node;
208
+ if (typeof forwardedRef === "function") {
209
+ forwardedRef(node);
210
+ } else if (forwardedRef) {
211
+ (forwardedRef as { current: HTMLDivElement | null }).current = node;
212
+ }
213
+ },
214
+ [forwardedRef],
215
+ );
216
+
217
+ // 첫 렌더에서 width=0이면 indicator를 숨겨 깜박임을 방지한다.
218
+ const indicatorVisible = indicatorRect.width > 0;
219
+
220
+ return (
221
+ <div
222
+ {...restProps}
223
+ ref={setRootRef}
224
+ role="radiogroup"
225
+ aria-label={ariaLabel}
226
+ className={clsx(
227
+ "segmented-control segmented-control-container",
228
+ className,
229
+ )}
230
+ data-keep-selected={keepSelected ? "true" : undefined}
231
+ >
232
+ {/* indicator와 list를 분리해 DOM 구조가 단순한 flex 계층을 유지한다. */}
233
+ <SegmentedControlIndicator
234
+ rect={indicatorRect}
235
+ visible={indicatorVisible}
236
+ />
237
+ <SegmentedControlList
238
+ options={options}
239
+ keepSelected={keepSelected}
240
+ selectedValue={selectedValue}
241
+ focusableIndex={focusableIndex}
242
+ fallbackIndex={fallbackIndex}
243
+ itemRefs={itemRefs}
244
+ onSelect={emitChange}
245
+ onFocusItemAt={focusItemAt}
246
+ onArrowNavigate={handleArrowNavigation}
247
+ />
248
+ </div>
249
+ );
250
+ },
251
+ );
252
+
253
+ SegmentedControl.displayName = "SegmentedControl";
254
+
255
+ export { SegmentedControl };
@@ -0,0 +1,31 @@
1
+ import type { SegmentedControlIndicatorProps } from "../types";
2
+
3
+ /**
4
+ * @component SegmentedControlIndicator
5
+ * @desc 선택된 항목 아래를 따라 이동하는 시각적 indicator 레이어.
6
+ * @param {SegmentedControlIndicatorProps} props indicator 렌더링에 사용되는 props.
7
+ * @param {SegmentedControlIndicatorRect} props.rect width/left 정보를 담은 사각형 값.
8
+ * @param {boolean} props.visible indicator 표시 여부.
9
+ */
10
+ const SegmentedControlIndicator = ({
11
+ rect,
12
+ visible,
13
+ }: SegmentedControlIndicatorProps) => {
14
+ const style = visible
15
+ ? {
16
+ width: `${rect.width}px`,
17
+ transform: `translateX(${rect.left}px)`,
18
+ }
19
+ : undefined;
20
+
21
+ return (
22
+ <figure
23
+ className="segmented-control-indicator"
24
+ style={style}
25
+ data-visible={visible ? "true" : "false"}
26
+ aria-hidden="true"
27
+ />
28
+ );
29
+ };
30
+
31
+ export { SegmentedControlIndicator };
@@ -0,0 +1,125 @@
1
+ import type {
2
+ SegmentedControlButtonEvent,
3
+ SegmentedControlListProps,
4
+ } from "../types";
5
+ import type { KeyboardEvent } from "react";
6
+
7
+ /**
8
+ * @component SegmentedControlList
9
+ * @desc Segmented Control의 버튼 리스트 레이어로, 포커스/키보드/keepSelected 로직을 캡슐화한다.
10
+ * @param {SegmentedControlListProps} props 리스트 렌더링에 사용되는 props.
11
+ * @param {SegmentedControlOption[]} props.options 렌더링할 옵션 배열.
12
+ * @param {boolean} props.keepSelected 동일 항목 재선택 시 해제 여부.
13
+ * @param {SegmentedControlValue | undefined} props.selectedValue 현재 선택된 값.
14
+ * @param {number} props.focusableIndex 포커스를 받을 기본 index.
15
+ * @param {number} props.fallbackIndex 첫 번째 활성화 index.
16
+ * @param {MutableRefObject<Array<HTMLButtonElement | null>>} props.itemRefs 버튼 ref 목록.
17
+ * @param {(value: SegmentedControlValue | undefined) => void} props.onSelect 값 변경 콜백.
18
+ * @param {(index: number) => void} props.onFocusItemAt index 포커스 함수.
19
+ * @param {(event: KeyboardEvent<HTMLButtonElement>, currentIndex: number) => void} props.onArrowNavigate 화살표 키 처리 콜백.
20
+ */
21
+ const SegmentedControlList = ({
22
+ options,
23
+ keepSelected,
24
+ selectedValue,
25
+ focusableIndex,
26
+ fallbackIndex,
27
+ itemRefs,
28
+ onSelect,
29
+ onFocusItemAt,
30
+ onArrowNavigate,
31
+ }: SegmentedControlListProps) => (
32
+ <ul className="segmented-control-list">
33
+ {options.map((option, index) => {
34
+ const isDisabled = Boolean(option.disabled);
35
+ const isSelected = selectedValue === option.value;
36
+ const tabIndex =
37
+ isDisabled || focusableIndex === -1
38
+ ? -1
39
+ : index === focusableIndex
40
+ ? 0
41
+ : -1;
42
+
43
+ // 모든 상호작용에서 공통으로 disabled 상태를 막는다.
44
+ const stopIfDisabled = (event: SegmentedControlButtonEvent) => {
45
+ if (!isDisabled) {
46
+ return false;
47
+ }
48
+ event.preventDefault();
49
+ event.stopPropagation();
50
+ return true;
51
+ };
52
+
53
+ // keepSelected=false일 때만 동일 버튼을 눌러 selection을 해제한다.
54
+ const handleClick = (event: SegmentedControlButtonEvent) => {
55
+ if (stopIfDisabled(event)) {
56
+ return;
57
+ }
58
+ if (!keepSelected && isSelected) {
59
+ onSelect(undefined);
60
+ return;
61
+ }
62
+ onSelect(option.value);
63
+ };
64
+
65
+ // Home/End는 양 끝으로, Arrow는 상위에서 전달된 로직을 호출한다.
66
+ const handleKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {
67
+ if (stopIfDisabled(event)) {
68
+ return;
69
+ }
70
+ if (event.key === "Home") {
71
+ event.preventDefault();
72
+ if (fallbackIndex >= 0) {
73
+ onFocusItemAt(fallbackIndex);
74
+ const fallbackOption = options[fallbackIndex];
75
+ if (fallbackOption) {
76
+ onSelect(fallbackOption.value);
77
+ }
78
+ }
79
+ return;
80
+ }
81
+ if (event.key === "End") {
82
+ event.preventDefault();
83
+ for (let i = options.length - 1; i >= 0; i -= 1) {
84
+ const candidate = options[i];
85
+ if (!candidate.disabled) {
86
+ onFocusItemAt(i);
87
+ onSelect(candidate.value);
88
+ break;
89
+ }
90
+ }
91
+ return;
92
+ }
93
+ onArrowNavigate(event, index);
94
+ };
95
+
96
+ return (
97
+ <li className="segmented-control-item" key={option.value}>
98
+ <button
99
+ ref={node => {
100
+ itemRefs.current[index] = node;
101
+ }}
102
+ type="button"
103
+ role="radio"
104
+ aria-checked={isSelected}
105
+ tabIndex={tabIndex}
106
+ className="segmented-control-button"
107
+ data-state={isSelected ? "on" : "off"}
108
+ data-disabled={isDisabled ? "true" : undefined}
109
+ onPointerDown={stopIfDisabled}
110
+ onFocus={stopIfDisabled}
111
+ onKeyDown={handleKeyDown}
112
+ onClick={handleClick}
113
+ disabled={isDisabled}
114
+ >
115
+ <span className="segmented-control-button-label">
116
+ {option.label}
117
+ </span>
118
+ </button>
119
+ </li>
120
+ );
121
+ })}
122
+ </ul>
123
+ );
124
+
125
+ export { SegmentedControlList };
@@ -1 +1 @@
1
- export * from "./SegmentedControl";
1
+ export * from "./Container";