@seed-design/lynx-react 0.5.1 → 0.6.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/lib/components/Checkbox/Checkbox.d.ts +26 -0
- package/lib/components/Checkbox/Checkbox.d.ts.map +1 -1
- package/lib/components/Checkbox/Checkbox.jsx +1 -1
- package/lib/components/Icon/Icon.d.ts +5 -0
- package/lib/components/Icon/Icon.d.ts.map +1 -1
- package/lib/components/Icon/Icon.jsx +14 -3
- package/lib/components/MannerTemp/MannerTemp.d.ts +19 -0
- package/lib/components/MannerTemp/MannerTemp.d.ts.map +1 -0
- package/lib/components/MannerTemp/MannerTemp.jsx +47 -0
- package/lib/components/MannerTemp/index.d.ts +2 -0
- package/lib/components/MannerTemp/index.d.ts.map +1 -0
- package/lib/components/MannerTempBadge/MannerTempBadge.d.ts +13 -0
- package/lib/components/MannerTempBadge/MannerTempBadge.d.ts.map +1 -0
- package/lib/components/MannerTempBadge/MannerTempBadge.jsx +15 -0
- package/lib/components/MannerTempBadge/index.d.ts +2 -0
- package/lib/components/MannerTempBadge/index.d.ts.map +1 -0
- package/lib/components/NotificationBadge/NotificationBadge.d.ts +18 -0
- package/lib/components/NotificationBadge/NotificationBadge.d.ts.map +1 -0
- package/lib/components/NotificationBadge/NotificationBadge.jsx +34 -0
- package/lib/components/NotificationBadge/index.d.ts +2 -0
- package/lib/components/NotificationBadge/index.d.ts.map +1 -0
- package/lib/components/PageBanner/PageBanner.d.ts +35 -0
- package/lib/components/PageBanner/PageBanner.d.ts.map +1 -0
- package/lib/components/PageBanner/PageBanner.jsx +150 -0
- package/lib/components/PageBanner/PageBanner.namespace.d.ts +2 -0
- package/lib/components/PageBanner/PageBanner.namespace.d.ts.map +1 -0
- package/lib/components/PageBanner/PageBanner.namespace.js +14 -0
- package/lib/components/PageBanner/index.d.ts +3 -0
- package/lib/components/PageBanner/index.d.ts.map +1 -0
- package/lib/components/RadioGroup/RadioGroup.d.ts +9 -0
- package/lib/components/RadioGroup/RadioGroup.d.ts.map +1 -1
- package/lib/components/RadioGroup/RadioGroup.jsx +3 -2
- package/lib/components/SegmentedControl/SegmentedControl.d.ts +27 -0
- package/lib/components/SegmentedControl/SegmentedControl.d.ts.map +1 -0
- package/lib/components/SegmentedControl/SegmentedControl.jsx +120 -0
- package/lib/components/SegmentedControl/SegmentedControl.namespace.d.ts +2 -0
- package/lib/components/SegmentedControl/SegmentedControl.namespace.d.ts.map +1 -0
- package/lib/components/SegmentedControl/SegmentedControl.namespace.js +10 -0
- package/lib/components/SegmentedControl/index.d.ts +3 -0
- package/lib/components/SegmentedControl/index.d.ts.map +1 -0
- package/lib/components/SelectBox/CheckSelectBox.namespace.d.ts +2 -0
- package/lib/components/SelectBox/CheckSelectBox.namespace.d.ts.map +1 -0
- package/lib/components/SelectBox/CheckSelectBox.namespace.js +17 -0
- package/lib/components/SelectBox/RadioSelectBox.namespace.d.ts +2 -0
- package/lib/components/SelectBox/RadioSelectBox.namespace.d.ts.map +1 -0
- package/lib/components/SelectBox/RadioSelectBox.namespace.js +15 -0
- package/lib/components/SelectBox/SelectBox.d.ts +85 -0
- package/lib/components/SelectBox/SelectBox.d.ts.map +1 -0
- package/lib/components/SelectBox/SelectBox.jsx +237 -0
- package/lib/components/SelectBox/index.d.ts +4 -0
- package/lib/components/SelectBox/index.d.ts.map +1 -0
- package/lib/components/Tabs/Tabs.d.ts +1 -1
- package/lib/components/Tabs/Tabs.d.ts.map +1 -1
- package/lib/components/Tabs/Tabs.jsx +10 -3
- package/lib/components/Tabs/Tabs.utils.d.ts +1 -0
- package/lib/components/Tabs/Tabs.utils.d.ts.map +1 -1
- package/lib/components/Tabs/Tabs.utils.js +4 -1
- package/lib/components/TextField/TextField.d.ts.map +1 -1
- package/lib/components/TextField/TextField.jsx +4 -0
- package/lib/components/index.d.ts +6 -0
- package/lib/components/index.d.ts.map +1 -1
- package/lib/hooks/useIconColor.d.ts +1 -0
- package/lib/hooks/useIconColor.d.ts.map +1 -1
- package/lib/hooks/useIconColor.js +4 -1
- package/lib/index.js +11 -1
- package/package.json +3 -3
- package/src/components/Checkbox/Checkbox.tsx +1 -1
- package/src/components/Icon/Icon.test.tsx +25 -1
- package/src/components/Icon/Icon.tsx +22 -1
- package/src/components/MannerTemp/MannerTemp.test.tsx +41 -0
- package/src/components/MannerTemp/MannerTemp.tsx +85 -0
- package/src/components/MannerTemp/index.ts +1 -0
- package/src/components/MannerTempBadge/MannerTempBadge.test.tsx +35 -0
- package/src/components/MannerTempBadge/MannerTempBadge.tsx +34 -0
- package/src/components/MannerTempBadge/index.ts +1 -0
- package/src/components/NotificationBadge/NotificationBadge.test.tsx +28 -0
- package/src/components/NotificationBadge/NotificationBadge.tsx +81 -0
- package/src/components/NotificationBadge/index.ts +1 -0
- package/src/components/PageBanner/PageBanner.namespace.ts +16 -0
- package/src/components/PageBanner/PageBanner.test.tsx +124 -0
- package/src/components/PageBanner/PageBanner.tsx +340 -0
- package/src/components/PageBanner/index.ts +18 -0
- package/src/components/RadioGroup/RadioGroup.tsx +3 -2
- package/src/components/SegmentedControl/SegmentedControl.namespace.ts +8 -0
- package/src/components/SegmentedControl/SegmentedControl.test.tsx +81 -0
- package/src/components/SegmentedControl/SegmentedControl.tsx +236 -0
- package/src/components/SegmentedControl/index.ts +10 -0
- package/src/components/SelectBox/CheckSelectBox.namespace.ts +22 -0
- package/src/components/SelectBox/RadioSelectBox.namespace.ts +18 -0
- package/src/components/SelectBox/SelectBox.test.tsx +169 -0
- package/src/components/SelectBox/SelectBox.tsx +533 -0
- package/src/components/SelectBox/index.ts +3 -0
- package/src/components/Tabs/Tabs.test.tsx +26 -1
- package/src/components/Tabs/Tabs.tsx +14 -2
- package/src/components/Tabs/Tabs.utils.ts +7 -0
- package/src/components/TextField/TextField.test.tsx +48 -0
- package/src/components/TextField/TextField.tsx +8 -1
- package/src/components/index.ts +6 -0
- package/src/hooks/useIconColor.ts +6 -1
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { segmentedControl } from "@seed-design/lynx-css/recipes/segmented-control";
|
|
2
|
+
import * as React from "@lynx-js/react";
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
|
|
5
|
+
import { useControllableState } from "../../hooks/useControllableState";
|
|
6
|
+
import { usePressTap } from "../../hooks/usePressTap";
|
|
7
|
+
import type {
|
|
8
|
+
LynxAccessibilityProps,
|
|
9
|
+
LynxPressableProps,
|
|
10
|
+
LynxStyledElementProps,
|
|
11
|
+
LynxViewProps,
|
|
12
|
+
LynxViewRef,
|
|
13
|
+
} from "../../types";
|
|
14
|
+
import { createSlotRecipeContext } from "../../utils/create-slot-recipe-context";
|
|
15
|
+
|
|
16
|
+
interface SegmentedControlContextValue {
|
|
17
|
+
value: string | undefined;
|
|
18
|
+
disabled: boolean;
|
|
19
|
+
items: string[];
|
|
20
|
+
selectValue: (value: string) => void;
|
|
21
|
+
registerItem: (value: string) => () => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const SegmentedControlContext = React.createContext<SegmentedControlContextValue | null>(null);
|
|
25
|
+
const { ClassNamesProvider, useClassNames } = createSlotRecipeContext(segmentedControl);
|
|
26
|
+
|
|
27
|
+
function useSegmentedControlContext(consumer: string) {
|
|
28
|
+
const context = React.useContext(SegmentedControlContext);
|
|
29
|
+
if (!context) {
|
|
30
|
+
throw new Error(`<${consumer}/> must be rendered inside <SegmentedControlRoot/>.`);
|
|
31
|
+
}
|
|
32
|
+
return context;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @platform Lynx
|
|
37
|
+
*
|
|
38
|
+
* 웹 대비 미지원 기능:
|
|
39
|
+
* - HiddenInput, name, form: Lynx에 HTML form 제출 모델이 없음
|
|
40
|
+
* - focus, focusVisible: Lynx에 키보드 포커스 모델이 없음
|
|
41
|
+
* - raw DOM change event: 선택 변경은 onValueChange로 노출
|
|
42
|
+
*/
|
|
43
|
+
export interface SegmentedControlRootProps extends LynxStyledElementProps, LynxAccessibilityProps {
|
|
44
|
+
value?: string;
|
|
45
|
+
defaultValue?: string;
|
|
46
|
+
disabled?: boolean;
|
|
47
|
+
onValueChange?: (value: string) => void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const SegmentedControlRoot = React.forwardRef<unknown, SegmentedControlRootProps>(
|
|
51
|
+
(props, ref) => {
|
|
52
|
+
const {
|
|
53
|
+
children,
|
|
54
|
+
className,
|
|
55
|
+
style,
|
|
56
|
+
value: valueProp,
|
|
57
|
+
defaultValue,
|
|
58
|
+
disabled = false,
|
|
59
|
+
onValueChange,
|
|
60
|
+
"accessibility-element": accessibilityElement = true,
|
|
61
|
+
"accessibility-role-description": accessibilityRoleDescription = "radiogroup",
|
|
62
|
+
...nativeProps
|
|
63
|
+
} = props;
|
|
64
|
+
const [value, setValue] = useControllableState<string | undefined>({
|
|
65
|
+
value: valueProp,
|
|
66
|
+
defaultValue,
|
|
67
|
+
onChange(nextValue) {
|
|
68
|
+
"background only";
|
|
69
|
+
if (nextValue !== undefined) onValueChange?.(nextValue);
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
const [items, setItems] = React.useState<string[]>([]);
|
|
73
|
+
|
|
74
|
+
const registerItem = React.useCallback((itemValue: string) => {
|
|
75
|
+
setItems((current) => {
|
|
76
|
+
if (current.includes(itemValue)) return current;
|
|
77
|
+
return [...current, itemValue];
|
|
78
|
+
});
|
|
79
|
+
return () => {
|
|
80
|
+
"background only";
|
|
81
|
+
setItems((current) => current.filter((item) => item !== itemValue));
|
|
82
|
+
};
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
const selectValue = React.useCallback(
|
|
86
|
+
(nextValue: string) => {
|
|
87
|
+
if (!disabled) setValue(nextValue);
|
|
88
|
+
},
|
|
89
|
+
[disabled, setValue],
|
|
90
|
+
);
|
|
91
|
+
const selectedIndex = items.indexOf(value ?? "");
|
|
92
|
+
const classNames = segmentedControl({ hasSelection: selectedIndex >= 0 });
|
|
93
|
+
const contextValue = React.useMemo<SegmentedControlContextValue>(
|
|
94
|
+
() => ({
|
|
95
|
+
value,
|
|
96
|
+
disabled,
|
|
97
|
+
items,
|
|
98
|
+
selectValue,
|
|
99
|
+
registerItem,
|
|
100
|
+
}),
|
|
101
|
+
[value, disabled, items, selectValue, registerItem],
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<SegmentedControlContext.Provider value={contextValue}>
|
|
106
|
+
<ClassNamesProvider value={classNames}>
|
|
107
|
+
<view
|
|
108
|
+
{...(ref ? { ref: ref as LynxViewRef } : {})}
|
|
109
|
+
{...nativeProps}
|
|
110
|
+
accessibility-element={accessibilityElement}
|
|
111
|
+
accessibility-role-description={accessibilityRoleDescription}
|
|
112
|
+
className={clsx(classNames.root, className)}
|
|
113
|
+
style={
|
|
114
|
+
{
|
|
115
|
+
"--segment-count": Math.max(items.length, 1).toString(),
|
|
116
|
+
"--segment-index": Math.max(selectedIndex, 0).toString(),
|
|
117
|
+
...style,
|
|
118
|
+
} as LynxViewProps["style"]
|
|
119
|
+
}
|
|
120
|
+
>
|
|
121
|
+
{children}
|
|
122
|
+
</view>
|
|
123
|
+
</ClassNamesProvider>
|
|
124
|
+
</SegmentedControlContext.Provider>
|
|
125
|
+
);
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
SegmentedControlRoot.displayName = "SegmentedControlRoot";
|
|
129
|
+
|
|
130
|
+
export interface SegmentedControlItemProps
|
|
131
|
+
extends Omit<LynxStyledElementProps, "children">,
|
|
132
|
+
LynxAccessibilityProps,
|
|
133
|
+
LynxPressableProps {
|
|
134
|
+
children: string | number;
|
|
135
|
+
value: string;
|
|
136
|
+
disabled?: boolean;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export const SegmentedControlItem = React.forwardRef<unknown, SegmentedControlItemProps>(
|
|
140
|
+
(props, ref) => {
|
|
141
|
+
const {
|
|
142
|
+
children,
|
|
143
|
+
className,
|
|
144
|
+
style,
|
|
145
|
+
value: itemValue,
|
|
146
|
+
disabled: itemDisabled = false,
|
|
147
|
+
bindtap,
|
|
148
|
+
"main-thread:bindtap": mainThreadBindtap,
|
|
149
|
+
"accessibility-element": accessibilityElement = true,
|
|
150
|
+
"accessibility-label": accessibilityLabel,
|
|
151
|
+
"accessibility-role-description": accessibilityRoleDescription = "radio",
|
|
152
|
+
"accessibility-traits": accessibilityTraits,
|
|
153
|
+
...nativeProps
|
|
154
|
+
} = props;
|
|
155
|
+
const context = useSegmentedControlContext("SegmentedControlItem");
|
|
156
|
+
const disabled = context.disabled || itemDisabled;
|
|
157
|
+
const selected = context.value === itemValue;
|
|
158
|
+
|
|
159
|
+
React.useEffect(() => {
|
|
160
|
+
"background only";
|
|
161
|
+
return context.registerItem(itemValue);
|
|
162
|
+
}, [context.registerItem, itemValue]);
|
|
163
|
+
|
|
164
|
+
const handleTap = React.useCallback(
|
|
165
|
+
(...args: Parameters<NonNullable<LynxPressableProps["bindtap"]>>) => {
|
|
166
|
+
"background only";
|
|
167
|
+
if (!selected) context.selectValue(itemValue);
|
|
168
|
+
bindtap?.(...args);
|
|
169
|
+
},
|
|
170
|
+
[bindtap, context.selectValue, itemValue, selected],
|
|
171
|
+
);
|
|
172
|
+
const pressSelectionRef = React.useRef(selected);
|
|
173
|
+
const { pressed, bindtouchstart, ...pressHandlers } = usePressTap({
|
|
174
|
+
disabled,
|
|
175
|
+
onTap: handleTap,
|
|
176
|
+
mainThreadOnTap: mainThreadBindtap,
|
|
177
|
+
});
|
|
178
|
+
const handleTouchStart = React.useCallback(
|
|
179
|
+
(...args: Parameters<typeof bindtouchstart>) => {
|
|
180
|
+
pressSelectionRef.current = selected;
|
|
181
|
+
bindtouchstart(...args);
|
|
182
|
+
},
|
|
183
|
+
[bindtouchstart, selected],
|
|
184
|
+
);
|
|
185
|
+
const classes = segmentedControl({ selected, disabled, pressed });
|
|
186
|
+
const pressStartClasses = segmentedControl({
|
|
187
|
+
selected: pressSelectionRef.current,
|
|
188
|
+
disabled,
|
|
189
|
+
pressed,
|
|
190
|
+
});
|
|
191
|
+
const label =
|
|
192
|
+
typeof children === "string" || typeof children === "number" ? String(children) : undefined;
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<view
|
|
196
|
+
{...(ref ? { ref: ref as LynxViewRef } : {})}
|
|
197
|
+
{...nativeProps}
|
|
198
|
+
bindtouchstart={handleTouchStart}
|
|
199
|
+
{...pressHandlers}
|
|
200
|
+
accessibility-element={accessibilityElement}
|
|
201
|
+
accessibility-label={accessibilityLabel ?? label}
|
|
202
|
+
accessibility-role-description={accessibilityRoleDescription}
|
|
203
|
+
accessibility-value={selected ? "selected" : "not selected"}
|
|
204
|
+
accessibility-traits={
|
|
205
|
+
disabled ? "disabled" : selected ? "selected" : (accessibilityTraits ?? "button")
|
|
206
|
+
}
|
|
207
|
+
className={clsx(classes.item, className)}
|
|
208
|
+
style={style}
|
|
209
|
+
>
|
|
210
|
+
<view accessibility-elements-hidden={true} className={pressStartClasses.itemBackground} />
|
|
211
|
+
<text className={classes.label}>{children}</text>
|
|
212
|
+
</view>
|
|
213
|
+
);
|
|
214
|
+
},
|
|
215
|
+
);
|
|
216
|
+
SegmentedControlItem.displayName = "SegmentedControlItem";
|
|
217
|
+
|
|
218
|
+
export interface SegmentedControlIndicatorProps extends LynxStyledElementProps {}
|
|
219
|
+
|
|
220
|
+
export const SegmentedControlIndicator = React.forwardRef<unknown, SegmentedControlIndicatorProps>(
|
|
221
|
+
(props, ref) => {
|
|
222
|
+
const { className, style, ...nativeProps } = props;
|
|
223
|
+
const classNames = useClassNames();
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<view
|
|
227
|
+
{...(ref ? { ref: ref as LynxViewRef } : {})}
|
|
228
|
+
{...nativeProps}
|
|
229
|
+
accessibility-elements-hidden={true}
|
|
230
|
+
className={clsx(classNames.indicator, className)}
|
|
231
|
+
style={style}
|
|
232
|
+
/>
|
|
233
|
+
);
|
|
234
|
+
},
|
|
235
|
+
);
|
|
236
|
+
SegmentedControlIndicator.displayName = "SegmentedControlIndicator";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export {
|
|
2
|
+
SegmentedControlIndicator,
|
|
3
|
+
SegmentedControlItem,
|
|
4
|
+
SegmentedControlRoot,
|
|
5
|
+
type SegmentedControlIndicatorProps,
|
|
6
|
+
type SegmentedControlItemProps,
|
|
7
|
+
type SegmentedControlRootProps,
|
|
8
|
+
} from "./SegmentedControl";
|
|
9
|
+
|
|
10
|
+
export * as SegmentedControl from "./SegmentedControl.namespace";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export {
|
|
2
|
+
CheckSelectBoxBody as Body,
|
|
3
|
+
CheckSelectBoxCheckmarkControl as CheckmarkControl,
|
|
4
|
+
CheckSelectBoxCheckmarkIcon as CheckmarkIcon,
|
|
5
|
+
CheckSelectBoxContent as Content,
|
|
6
|
+
CheckSelectBoxDescription as Description,
|
|
7
|
+
CheckSelectBoxFooter as Footer,
|
|
8
|
+
CheckSelectBoxGroup as Group,
|
|
9
|
+
CheckSelectBoxLabel as Label,
|
|
10
|
+
CheckSelectBoxRoot as Root,
|
|
11
|
+
CheckSelectBoxTrigger as Trigger,
|
|
12
|
+
type CheckSelectBoxBodyProps as BodyProps,
|
|
13
|
+
type CheckSelectBoxCheckmarkControlProps as CheckmarkControlProps,
|
|
14
|
+
type CheckSelectBoxCheckmarkIconProps as CheckmarkIconProps,
|
|
15
|
+
type CheckSelectBoxContentProps as ContentProps,
|
|
16
|
+
type CheckSelectBoxDescriptionProps as DescriptionProps,
|
|
17
|
+
type CheckSelectBoxFooterProps as FooterProps,
|
|
18
|
+
type CheckSelectBoxGroupProps as GroupProps,
|
|
19
|
+
type CheckSelectBoxLabelProps as LabelProps,
|
|
20
|
+
type CheckSelectBoxRootProps as RootProps,
|
|
21
|
+
type CheckSelectBoxTriggerProps as TriggerProps,
|
|
22
|
+
} from "./SelectBox";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export {
|
|
2
|
+
RadioSelectBoxBody as Body,
|
|
3
|
+
RadioSelectBoxContent as Content,
|
|
4
|
+
RadioSelectBoxDescription as Description,
|
|
5
|
+
RadioSelectBoxFooter as Footer,
|
|
6
|
+
RadioSelectBoxGroup as Group,
|
|
7
|
+
RadioSelectBoxItem as Item,
|
|
8
|
+
RadioSelectBoxLabel as Label,
|
|
9
|
+
RadioSelectBoxTrigger as Trigger,
|
|
10
|
+
type RadioSelectBoxBodyProps as BodyProps,
|
|
11
|
+
type RadioSelectBoxContentProps as ContentProps,
|
|
12
|
+
type RadioSelectBoxDescriptionProps as DescriptionProps,
|
|
13
|
+
type RadioSelectBoxFooterProps as FooterProps,
|
|
14
|
+
type RadioSelectBoxGroupProps as GroupProps,
|
|
15
|
+
type RadioSelectBoxItemProps as ItemProps,
|
|
16
|
+
type RadioSelectBoxLabelProps as LabelProps,
|
|
17
|
+
type RadioSelectBoxTriggerProps as TriggerProps,
|
|
18
|
+
} from "./SelectBox";
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import { act, fireEvent, render } from "@lynx-js/react/testing-library";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { RadioGroup } from "../RadioGroup";
|
|
6
|
+
import { CheckSelectBox, RadioSelectBox } from "./index";
|
|
7
|
+
|
|
8
|
+
function getRenderedRoot() {
|
|
9
|
+
const root = elementTree.root;
|
|
10
|
+
if (!root) throw new Error("Expected Lynx render root to exist.");
|
|
11
|
+
return root;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function CheckItem() {
|
|
15
|
+
return (
|
|
16
|
+
<CheckSelectBox.Root accessibility-label="마케팅 정보 수신">
|
|
17
|
+
<CheckSelectBox.Trigger>
|
|
18
|
+
<CheckSelectBox.Content>
|
|
19
|
+
<CheckSelectBox.Body>
|
|
20
|
+
<CheckSelectBox.Label>마케팅 정보 수신</CheckSelectBox.Label>
|
|
21
|
+
</CheckSelectBox.Body>
|
|
22
|
+
</CheckSelectBox.Content>
|
|
23
|
+
</CheckSelectBox.Trigger>
|
|
24
|
+
<CheckSelectBox.Footer className="check-footer">
|
|
25
|
+
<text>혜택 알림을 받습니다.</text>
|
|
26
|
+
</CheckSelectBox.Footer>
|
|
27
|
+
</CheckSelectBox.Root>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe("SelectBox", () => {
|
|
32
|
+
it("lays out custom label content in a view", () => {
|
|
33
|
+
render(
|
|
34
|
+
<CheckSelectBox.Root accessibility-label="Melon New">
|
|
35
|
+
<CheckSelectBox.Trigger>
|
|
36
|
+
<CheckSelectBox.Label>
|
|
37
|
+
<text>Melon</text>
|
|
38
|
+
<view className="label-badge">
|
|
39
|
+
<text>New</text>
|
|
40
|
+
</view>
|
|
41
|
+
</CheckSelectBox.Label>
|
|
42
|
+
</CheckSelectBox.Trigger>
|
|
43
|
+
</CheckSelectBox.Root>,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const label = getRenderedRoot().querySelector<HTMLElement>(".seed-select-box__label");
|
|
47
|
+
|
|
48
|
+
expect(label?.tagName.toLowerCase()).toBe("view");
|
|
49
|
+
expect(label?.querySelector(".label-badge")).toBeInTheDocument();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("renders an initially open footer without starting from zero height", () => {
|
|
53
|
+
render(
|
|
54
|
+
<CheckSelectBox.Root accessibility-label="마케팅 정보 수신" defaultChecked>
|
|
55
|
+
<CheckSelectBox.Trigger>
|
|
56
|
+
<CheckSelectBox.Label>마케팅 정보 수신</CheckSelectBox.Label>
|
|
57
|
+
</CheckSelectBox.Trigger>
|
|
58
|
+
<CheckSelectBox.Footer className="check-footer">
|
|
59
|
+
<text>혜택 알림을 받습니다.</text>
|
|
60
|
+
</CheckSelectBox.Footer>
|
|
61
|
+
</CheckSelectBox.Root>,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const root = getRenderedRoot();
|
|
65
|
+
const footer = root.querySelector<HTMLElement>(".check-footer");
|
|
66
|
+
const footerInner = footer?.querySelector<HTMLElement>(".seed-select-box__footerInner");
|
|
67
|
+
|
|
68
|
+
expect(footer).toHaveStyle({ height: "auto" });
|
|
69
|
+
|
|
70
|
+
act(() => {
|
|
71
|
+
fireEvent.layoutchange(footerInner as HTMLElement, { height: 48 });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(footer).toHaveStyle({ height: "48px" });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("toggles a check item and reveals the measured footer", () => {
|
|
78
|
+
const onCheckedChange = vi.fn();
|
|
79
|
+
render(
|
|
80
|
+
<CheckSelectBox.Root accessibility-label="마케팅 정보 수신" onCheckedChange={onCheckedChange}>
|
|
81
|
+
<CheckSelectBox.Trigger>
|
|
82
|
+
<CheckSelectBox.Label>마케팅 정보 수신</CheckSelectBox.Label>
|
|
83
|
+
</CheckSelectBox.Trigger>
|
|
84
|
+
<CheckSelectBox.Footer className="check-footer">
|
|
85
|
+
<text>혜택 알림을 받습니다.</text>
|
|
86
|
+
</CheckSelectBox.Footer>
|
|
87
|
+
</CheckSelectBox.Root>,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const root = getRenderedRoot();
|
|
91
|
+
const interactionRoot = root.querySelector<HTMLElement>(".seed-select-box__interactionRoot");
|
|
92
|
+
const surface = root.querySelector<HTMLElement>(".seed-select-box__root");
|
|
93
|
+
const footer = root.querySelector<HTMLElement>(".check-footer");
|
|
94
|
+
const footerInner = footer?.querySelector<HTMLElement>(".seed-select-box__footerInner");
|
|
95
|
+
|
|
96
|
+
expect(surface).toHaveAttribute("accessibility-value", "not selected");
|
|
97
|
+
expect(footer).toHaveStyle({ height: "0px" });
|
|
98
|
+
expect(footer).toHaveAttribute("accessibility-elements-hidden", "true");
|
|
99
|
+
|
|
100
|
+
act(() => {
|
|
101
|
+
fireEvent.layoutchange(footerInner as HTMLElement, { height: 48 });
|
|
102
|
+
});
|
|
103
|
+
fireEvent.tap(interactionRoot as HTMLElement);
|
|
104
|
+
|
|
105
|
+
expect(onCheckedChange).toHaveBeenCalledWith(true);
|
|
106
|
+
expect(surface).toHaveAttribute("accessibility-value", "selected");
|
|
107
|
+
expect(footer).toHaveStyle({ height: "48px" });
|
|
108
|
+
expect(footer).toHaveAttribute("accessibility-elements-hidden", "false");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("uses vertical item layout for a multi-column group", () => {
|
|
112
|
+
render(
|
|
113
|
+
<CheckSelectBox.Group columns={2} className="select-box-group">
|
|
114
|
+
<CheckItem />
|
|
115
|
+
<CheckItem />
|
|
116
|
+
</CheckSelectBox.Group>,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const root = getRenderedRoot();
|
|
120
|
+
const group = root.querySelector<HTMLElement>(".select-box-group");
|
|
121
|
+
const trigger = root.querySelector<HTMLElement>(".seed-select-box__trigger");
|
|
122
|
+
|
|
123
|
+
expect(group).toHaveStyle({ gridTemplateColumns: "repeat(2, 1fr)" });
|
|
124
|
+
expect(trigger?.className).toContain("seed-select-box__trigger--layout_vertical");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("selects one radio item and ignores a disabled item", () => {
|
|
128
|
+
const onValueChange = vi.fn();
|
|
129
|
+
render(
|
|
130
|
+
<RadioGroup.Root defaultValue="first" onValueChange={onValueChange}>
|
|
131
|
+
<RadioSelectBox.Group>
|
|
132
|
+
<RadioSelectBox.Item value="first" accessibility-label="첫 번째">
|
|
133
|
+
<RadioSelectBox.Trigger>
|
|
134
|
+
<RadioSelectBox.Label>첫 번째</RadioSelectBox.Label>
|
|
135
|
+
</RadioSelectBox.Trigger>
|
|
136
|
+
</RadioSelectBox.Item>
|
|
137
|
+
<RadioSelectBox.Item value="second" accessibility-label="두 번째">
|
|
138
|
+
<RadioSelectBox.Trigger>
|
|
139
|
+
<RadioSelectBox.Label>두 번째</RadioSelectBox.Label>
|
|
140
|
+
</RadioSelectBox.Trigger>
|
|
141
|
+
</RadioSelectBox.Item>
|
|
142
|
+
<RadioSelectBox.Item value="disabled" accessibility-label="비활성" disabled>
|
|
143
|
+
<RadioSelectBox.Trigger>
|
|
144
|
+
<RadioSelectBox.Label>비활성</RadioSelectBox.Label>
|
|
145
|
+
</RadioSelectBox.Trigger>
|
|
146
|
+
</RadioSelectBox.Item>
|
|
147
|
+
</RadioSelectBox.Group>
|
|
148
|
+
</RadioGroup.Root>,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const root = getRenderedRoot();
|
|
152
|
+
const interactionRoots = root.querySelectorAll<HTMLElement>(
|
|
153
|
+
".seed-select-box__interactionRoot",
|
|
154
|
+
);
|
|
155
|
+
const surfaces = root.querySelectorAll<HTMLElement>(".seed-select-box__root");
|
|
156
|
+
|
|
157
|
+
expect(surfaces[0]).toHaveAttribute("accessibility-value", "selected");
|
|
158
|
+
expect(surfaces[1]).toHaveAttribute("accessibility-value", "not selected");
|
|
159
|
+
|
|
160
|
+
fireEvent.tap(interactionRoots[1]);
|
|
161
|
+
expect(onValueChange).toHaveBeenCalledWith("second");
|
|
162
|
+
expect(surfaces[0]).toHaveAttribute("accessibility-value", "not selected");
|
|
163
|
+
expect(surfaces[1]).toHaveAttribute("accessibility-value", "selected");
|
|
164
|
+
|
|
165
|
+
fireEvent.tap(interactionRoots[2]);
|
|
166
|
+
expect(onValueChange).toHaveBeenCalledTimes(1);
|
|
167
|
+
expect(surfaces[2]).toHaveAttribute("accessibility-traits", "disabled");
|
|
168
|
+
});
|
|
169
|
+
});
|