@usefui/components 1.6.0 → 1.7.1
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/CHANGELOG.md +12 -0
- package/dist/index.d.mts +380 -52
- package/dist/index.d.ts +380 -52
- package/dist/index.js +2532 -511
- package/dist/index.mjs +2518 -508
- package/package.json +3 -3
- package/src/__tests__/Avatar.test.tsx +55 -55
- package/src/accordion/Accordion.stories.tsx +6 -4
- package/src/accordion/index.tsx +1 -2
- package/src/avatar/Avatar.stories.tsx +37 -7
- package/src/avatar/index.tsx +90 -19
- package/src/avatar/styles/index.ts +58 -12
- package/src/badge/Badge.stories.tsx +27 -5
- package/src/badge/index.tsx +21 -13
- package/src/badge/styles/index.ts +69 -40
- package/src/button/Button.stories.tsx +40 -27
- package/src/button/index.tsx +13 -9
- package/src/button/styles/index.ts +308 -47
- package/src/card/index.tsx +2 -4
- package/src/checkbox/Checkbox.stories.tsx +72 -33
- package/src/checkbox/index.tsx +8 -6
- package/src/checkbox/styles/index.ts +239 -19
- package/src/collapsible/Collapsible.stories.tsx +6 -4
- package/src/dialog/Dialog.stories.tsx +173 -31
- package/src/dialog/styles/index.ts +13 -8
- package/src/dropdown/Dropdown.stories.tsx +61 -23
- package/src/dropdown/index.tsx +42 -31
- package/src/dropdown/styles/index.ts +30 -19
- package/src/field/Field.stories.tsx +183 -24
- package/src/field/index.tsx +930 -13
- package/src/field/styles/index.ts +246 -14
- package/src/field/types/index.ts +31 -0
- package/src/field/utils/index.ts +201 -0
- package/src/index.ts +2 -1
- package/src/message-bubble/MessageBubble.stories.tsx +59 -12
- package/src/message-bubble/index.tsx +22 -4
- package/src/message-bubble/styles/index.ts +4 -7
- package/src/otp-field/OTPField.stories.tsx +22 -24
- package/src/otp-field/index.tsx +9 -0
- package/src/otp-field/styles/index.ts +114 -16
- package/src/otp-field/types/index.ts +9 -1
- package/src/overlay/styles/index.ts +1 -0
- package/src/ruler/Ruler.stories.tsx +43 -0
- package/src/ruler/constants/index.ts +3 -0
- package/src/ruler/hooks/index.tsx +53 -0
- package/src/ruler/index.tsx +239 -0
- package/src/ruler/styles/index.tsx +154 -0
- package/src/ruler/types/index.ts +17 -0
- package/src/select/Select.stories.tsx +91 -0
- package/src/select/hooks/index.tsx +71 -0
- package/src/select/index.tsx +331 -0
- package/src/select/styles/index.tsx +156 -0
- package/src/shimmer/Shimmer.stories.tsx +6 -4
- package/src/skeleton/index.tsx +7 -6
- package/src/spinner/Spinner.stories.tsx +29 -4
- package/src/spinner/index.tsx +16 -6
- package/src/spinner/styles/index.ts +41 -22
- package/src/switch/Switch.stories.tsx +46 -17
- package/src/switch/index.tsx +5 -8
- package/src/switch/styles/index.ts +45 -45
- package/src/tabs/Tabs.stories.tsx +43 -15
- package/src/text-area/Textarea.stories.tsx +45 -8
- package/src/text-area/index.tsx +9 -6
- package/src/text-area/styles/index.ts +1 -1
- package/src/toggle/Toggle.stories.tsx +6 -4
- package/src/tree/Tree.stories.tsx +6 -4
- package/src/privacy-field/PrivacyField.stories.tsx +0 -29
- package/src/privacy-field/index.tsx +0 -56
- package/src/privacy-field/styles/index.ts +0 -17
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { useRuler, RulerProvider } from "./hooks";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
HorizontalRuler,
|
|
8
|
+
VerticalRuler,
|
|
9
|
+
CornerSquare,
|
|
10
|
+
HTickContainer,
|
|
11
|
+
VTickContainer,
|
|
12
|
+
HTickMark,
|
|
13
|
+
VTickMark,
|
|
14
|
+
HTickLabel,
|
|
15
|
+
VTickLabel,
|
|
16
|
+
VerticalGuideLine,
|
|
17
|
+
HorizontalGuideLine,
|
|
18
|
+
CanvasContent,
|
|
19
|
+
CanvasWrapper,
|
|
20
|
+
} from "./styles";
|
|
21
|
+
|
|
22
|
+
import { MAJOR_TICK_INTERVAL, MINOR_TICK_INTERVAL } from "./constants";
|
|
23
|
+
import { Guide } from "./types";
|
|
24
|
+
import { IReactChildren } from "../../../../types";
|
|
25
|
+
|
|
26
|
+
export interface RulerComposition {
|
|
27
|
+
Root: typeof RulerRoot;
|
|
28
|
+
Row: typeof RulerRow;
|
|
29
|
+
Corner: typeof RulerCorner;
|
|
30
|
+
Lines: typeof RulerLines;
|
|
31
|
+
Canvas: typeof RulerCanvas;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const Ruler = ({ children }: IReactChildren) => {
|
|
35
|
+
return <CanvasWrapper>{children}</CanvasWrapper>;
|
|
36
|
+
};
|
|
37
|
+
Ruler.displayName = "Ruler";
|
|
38
|
+
|
|
39
|
+
const RulerRoot = ({ children }: IReactChildren) => {
|
|
40
|
+
return <RulerProvider>{children}</RulerProvider>;
|
|
41
|
+
};
|
|
42
|
+
RulerRoot.displayName = "Ruler.Root";
|
|
43
|
+
|
|
44
|
+
const RulerRow = ({
|
|
45
|
+
orientation,
|
|
46
|
+
}: {
|
|
47
|
+
orientation: "horizontal" | "vertical";
|
|
48
|
+
}) => {
|
|
49
|
+
const { addGuide, setActiveGuide, setIsDragging, canvasRef } = useRuler();
|
|
50
|
+
|
|
51
|
+
const rulerRef = React.useRef<HTMLDivElement>(null);
|
|
52
|
+
const [size, setSize] = React.useState(0);
|
|
53
|
+
|
|
54
|
+
const isH = orientation === "horizontal";
|
|
55
|
+
|
|
56
|
+
React.useEffect(() => {
|
|
57
|
+
const update = () => {
|
|
58
|
+
if (canvasRef.current) {
|
|
59
|
+
setSize(
|
|
60
|
+
isH ? canvasRef.current.offsetWidth : canvasRef.current.offsetHeight,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
update();
|
|
65
|
+
window.addEventListener("resize", update);
|
|
66
|
+
return () => window.removeEventListener("resize", update);
|
|
67
|
+
}, [canvasRef, isH]);
|
|
68
|
+
|
|
69
|
+
const getPos = (e: React.MouseEvent) => {
|
|
70
|
+
const rect = rulerRef.current?.getBoundingClientRect();
|
|
71
|
+
if (!rect) return null;
|
|
72
|
+
return isH ? e.clientX - rect.left : e.clientY - rect.top;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const handleMouseDown = (e: React.MouseEvent) => {
|
|
76
|
+
const position = getPos(e);
|
|
77
|
+
if (position === null) return;
|
|
78
|
+
const guide = addGuide({
|
|
79
|
+
position: 0,
|
|
80
|
+
orientation: isH ? "horizontal" : "vertical",
|
|
81
|
+
}) as any;
|
|
82
|
+
setActiveGuide(guide);
|
|
83
|
+
setIsDragging(true);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const ticks = Array.from(
|
|
87
|
+
{ length: Math.floor(size / MINOR_TICK_INTERVAL) + 1 },
|
|
88
|
+
(_, idx) => {
|
|
89
|
+
const i = idx * MINOR_TICK_INTERVAL;
|
|
90
|
+
const isMajor = i % MAJOR_TICK_INTERVAL === 0;
|
|
91
|
+
return isH ? (
|
|
92
|
+
<HTickContainer key={i} $x={i}>
|
|
93
|
+
<HTickMark $major={isMajor} />
|
|
94
|
+
{isMajor && <HTickLabel>{i}</HTickLabel>}
|
|
95
|
+
</HTickContainer>
|
|
96
|
+
) : (
|
|
97
|
+
<VTickContainer key={i} $y={i}>
|
|
98
|
+
<VTickMark $major={isMajor} />
|
|
99
|
+
{isMajor && <VTickLabel>{i}</VTickLabel>}
|
|
100
|
+
</VTickContainer>
|
|
101
|
+
);
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const RulerEl = isH ? HorizontalRuler : VerticalRuler;
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<RulerEl ref={rulerRef} onMouseDown={handleMouseDown}>
|
|
109
|
+
{ticks}
|
|
110
|
+
</RulerEl>
|
|
111
|
+
);
|
|
112
|
+
};
|
|
113
|
+
RulerRow.displayName = "Ruler.Row";
|
|
114
|
+
|
|
115
|
+
const RulerCorner = () => {
|
|
116
|
+
return <CornerSquare />;
|
|
117
|
+
};
|
|
118
|
+
RulerCorner.displayName = "Ruler.Corner";
|
|
119
|
+
|
|
120
|
+
const RulerLine = ({ guide }: { guide: Guide }) => {
|
|
121
|
+
const [isHovered, setIsHovered] = React.useState(false);
|
|
122
|
+
const { activeGuide, removeGuide, setActiveGuide, setIsDragging } =
|
|
123
|
+
useRuler();
|
|
124
|
+
|
|
125
|
+
const highlighted = activeGuide?.id === guide.id || isHovered;
|
|
126
|
+
|
|
127
|
+
const sharedProps = {
|
|
128
|
+
$active: highlighted,
|
|
129
|
+
onMouseDown(e: React.MouseEvent) {
|
|
130
|
+
e.stopPropagation();
|
|
131
|
+
setActiveGuide(guide);
|
|
132
|
+
setIsDragging(true);
|
|
133
|
+
},
|
|
134
|
+
onDoubleClick(e: React.MouseEvent) {
|
|
135
|
+
e.stopPropagation();
|
|
136
|
+
removeGuide(guide.id);
|
|
137
|
+
},
|
|
138
|
+
onMouseEnter() {
|
|
139
|
+
setIsHovered(true);
|
|
140
|
+
},
|
|
141
|
+
onMouseLeave() {
|
|
142
|
+
setIsHovered(false);
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
if (guide.orientation === "vertical") {
|
|
147
|
+
return <VerticalGuideLine $x={guide.position} {...sharedProps} />;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return <HorizontalGuideLine $y={guide.position} {...sharedProps} />;
|
|
151
|
+
};
|
|
152
|
+
RulerLine.displayName = "Ruler.Line";
|
|
153
|
+
|
|
154
|
+
const RulerLines = () => {
|
|
155
|
+
const {
|
|
156
|
+
guides,
|
|
157
|
+
activeGuide,
|
|
158
|
+
updateGuide,
|
|
159
|
+
removeGuide,
|
|
160
|
+
setActiveGuide,
|
|
161
|
+
isDragging,
|
|
162
|
+
setIsDragging,
|
|
163
|
+
canvasRef,
|
|
164
|
+
} = useRuler();
|
|
165
|
+
|
|
166
|
+
React.useEffect(() => {
|
|
167
|
+
if (!isDragging || !activeGuide) return;
|
|
168
|
+
|
|
169
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
170
|
+
const canvas = canvasRef.current;
|
|
171
|
+
if (!canvas) return;
|
|
172
|
+
const rect = canvas.getBoundingClientRect();
|
|
173
|
+
const pos =
|
|
174
|
+
activeGuide.orientation === "vertical"
|
|
175
|
+
? e.clientX - rect.left
|
|
176
|
+
: e.clientY - rect.top;
|
|
177
|
+
|
|
178
|
+
updateGuide(activeGuide.id, Math.max(0, pos));
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const handleMouseUp = (e: MouseEvent) => {
|
|
182
|
+
const canvas = canvasRef.current;
|
|
183
|
+
if (canvas && activeGuide) {
|
|
184
|
+
const rect = canvas.getBoundingClientRect();
|
|
185
|
+
const isOutside =
|
|
186
|
+
e.clientX < rect.left ||
|
|
187
|
+
e.clientX > rect.right ||
|
|
188
|
+
e.clientY < rect.top ||
|
|
189
|
+
e.clientY > rect.bottom;
|
|
190
|
+
if (isOutside) removeGuide(activeGuide.id);
|
|
191
|
+
}
|
|
192
|
+
setIsDragging(false);
|
|
193
|
+
setActiveGuide(null);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
197
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
198
|
+
return () => {
|
|
199
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
200
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
201
|
+
};
|
|
202
|
+
}, [
|
|
203
|
+
isDragging,
|
|
204
|
+
activeGuide,
|
|
205
|
+
updateGuide,
|
|
206
|
+
removeGuide,
|
|
207
|
+
setActiveGuide,
|
|
208
|
+
setIsDragging,
|
|
209
|
+
canvasRef,
|
|
210
|
+
]);
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<React.Fragment>
|
|
214
|
+
{guides.map((guide) => (
|
|
215
|
+
<RulerLine key={guide.id} guide={guide} />
|
|
216
|
+
))}
|
|
217
|
+
</React.Fragment>
|
|
218
|
+
);
|
|
219
|
+
};
|
|
220
|
+
RulerLines.displayName = "Ruler.Lines";
|
|
221
|
+
|
|
222
|
+
const RulerCanvas = ({ children }: IReactChildren) => {
|
|
223
|
+
const { canvasRef, isDragging } = useRuler();
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<CanvasContent ref={canvasRef} $isDragging={isDragging}>
|
|
227
|
+
{children}
|
|
228
|
+
</CanvasContent>
|
|
229
|
+
);
|
|
230
|
+
};
|
|
231
|
+
RulerCanvas.displayName = "Ruler.Canvas";
|
|
232
|
+
|
|
233
|
+
Ruler.Root = RulerRoot;
|
|
234
|
+
Ruler.Row = RulerRow;
|
|
235
|
+
Ruler.Corner = RulerCorner;
|
|
236
|
+
Ruler.Lines = RulerLines;
|
|
237
|
+
Ruler.Canvas = RulerCanvas;
|
|
238
|
+
|
|
239
|
+
export { Ruler, RulerRoot, RulerRow, RulerCorner, RulerLines, RulerCanvas };
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import styled, { css } from "styled-components";
|
|
2
|
+
import { RULER_SIZE } from "../constants";
|
|
3
|
+
|
|
4
|
+
const activeGuideStyles = css`
|
|
5
|
+
background-color: var(--color-orange);
|
|
6
|
+
transition: background ease-in-out 0.2s;
|
|
7
|
+
`;
|
|
8
|
+
|
|
9
|
+
const inactiveGuideStyles = css`
|
|
10
|
+
background-color: var(--alpha-orange-60);
|
|
11
|
+
transition: background ease-in-out 0.2s;
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
export const RulerBase: any = styled.div`
|
|
15
|
+
position: absolute;
|
|
16
|
+
background-color: transparent;
|
|
17
|
+
user-select: none;
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
z-index: var(--depth-default-100);
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
export const HorizontalRuler: any = styled(RulerBase)`
|
|
23
|
+
top: 0;
|
|
24
|
+
left: ${RULER_SIZE}px;
|
|
25
|
+
right: 0;
|
|
26
|
+
height: var(--measurement-medium-70);
|
|
27
|
+
/* border-bottom: var(--measurement-small-30) solid var(--font-color-alpha-10); */
|
|
28
|
+
cursor: s-resize;
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
export const VerticalRuler: any = styled(RulerBase)`
|
|
32
|
+
top: ${RULER_SIZE}px;
|
|
33
|
+
left: 0;
|
|
34
|
+
bottom: 0;
|
|
35
|
+
width: ${RULER_SIZE}px;
|
|
36
|
+
/* border-right: var(--measurement-small-30) solid var(--font-color-alpha-10); */
|
|
37
|
+
cursor: e-resize;
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
export const CornerSquare: any = styled.div`
|
|
41
|
+
position: absolute;
|
|
42
|
+
top: 0;
|
|
43
|
+
left: 0;
|
|
44
|
+
width: ${RULER_SIZE}px;
|
|
45
|
+
height: ${RULER_SIZE}px;
|
|
46
|
+
background-color: transparent;
|
|
47
|
+
|
|
48
|
+
z-index: var(--depth-default-100);
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
export const HTickContainer: any = styled.div<{ $x: number }>`
|
|
52
|
+
position: absolute;
|
|
53
|
+
top: 0;
|
|
54
|
+
left: ${({ $x }) => $x}px;
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
export const VTickContainer: any = styled.div<{ $y: number }>`
|
|
58
|
+
position: absolute;
|
|
59
|
+
left: 0;
|
|
60
|
+
top: ${({ $y }) => $y}px;
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
export const HTickMark: any = styled.div<{ $major: boolean }>`
|
|
64
|
+
width: var(--measurement-small-30);
|
|
65
|
+
height: ${({ $major }) => ($major ? 6 : 3)}px;
|
|
66
|
+
background-color: ${({ $major }) =>
|
|
67
|
+
$major ? "var(--font-color-alpha-20)" : "var(--font-color-alpha-10)"};
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
export const VTickMark: any = styled.div<{ $major: boolean }>`
|
|
71
|
+
height: var(--measurement-small-30);
|
|
72
|
+
width: ${({ $major }) => ($major ? 6 : 3)}px;
|
|
73
|
+
background-color: ${({ $major }) =>
|
|
74
|
+
$major ? "var(--font-color-alpha-30)" : "var(--font-color-alpha-10)"};
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
export const HTickLabel: any = styled.span`
|
|
78
|
+
position: absolute;
|
|
79
|
+
|
|
80
|
+
top: var(--fontsize-small-30);
|
|
81
|
+
left: calc(var(--fontsize-small-30) / 1.25 * -1);
|
|
82
|
+
|
|
83
|
+
font-size: var(--fontsize-small-30);
|
|
84
|
+
color: var(--font-color-alpha-30);
|
|
85
|
+
|
|
86
|
+
font-family: monospace;
|
|
87
|
+
user-select: none;
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
export const VTickLabel: any = styled.span`
|
|
91
|
+
position: absolute;
|
|
92
|
+
|
|
93
|
+
top: calc(var(--fontsize-small-30) / 1.25 * -1);
|
|
94
|
+
left: var(--fontsize-small-30);
|
|
95
|
+
|
|
96
|
+
font-size: var(--fontsize-small-30);
|
|
97
|
+
color: var(--font-color-alpha-30);
|
|
98
|
+
|
|
99
|
+
font-family: monospace;
|
|
100
|
+
user-select: none;
|
|
101
|
+
|
|
102
|
+
writing-mode: vertical-rl;
|
|
103
|
+
transform: rotate(180deg);
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
export const VerticalGuideLine: any = styled.div<{
|
|
107
|
+
$x: number;
|
|
108
|
+
$active: boolean;
|
|
109
|
+
}>`
|
|
110
|
+
position: absolute;
|
|
111
|
+
top: 0;
|
|
112
|
+
bottom: 0;
|
|
113
|
+
width: var(--measurement-small-30);
|
|
114
|
+
cursor: col-resize;
|
|
115
|
+
z-index: var(--depth-default-90);
|
|
116
|
+
left: ${({ $x }) => $x}px;
|
|
117
|
+
${({ $active }) => ($active ? activeGuideStyles : inactiveGuideStyles)}
|
|
118
|
+
`;
|
|
119
|
+
|
|
120
|
+
export const HorizontalGuideLine: any = styled.div<{
|
|
121
|
+
$y: number;
|
|
122
|
+
$active: boolean;
|
|
123
|
+
}>`
|
|
124
|
+
position: absolute;
|
|
125
|
+
left: 0;
|
|
126
|
+
right: 0;
|
|
127
|
+
height: var(--measurement-small-30);
|
|
128
|
+
cursor: row-resize;
|
|
129
|
+
z-index: var(--depth-default-90);
|
|
130
|
+
top: ${({ $y }) => $y}px;
|
|
131
|
+
${({ $active }) => ($active ? activeGuideStyles : inactiveGuideStyles)}
|
|
132
|
+
|
|
133
|
+
transition: bakground ease-in-out 0.2s;
|
|
134
|
+
`;
|
|
135
|
+
|
|
136
|
+
export const CanvasContent: any = styled.div<{ $isDragging?: boolean }>`
|
|
137
|
+
position: absolute;
|
|
138
|
+
inset: 0;
|
|
139
|
+
overflow: hidden;
|
|
140
|
+
|
|
141
|
+
${({ $isDragging }) =>
|
|
142
|
+
$isDragging &&
|
|
143
|
+
css`
|
|
144
|
+
pointer-events: none;
|
|
145
|
+
user-select: none;
|
|
146
|
+
`}
|
|
147
|
+
`;
|
|
148
|
+
|
|
149
|
+
export const CanvasWrapper: any = styled.div`
|
|
150
|
+
position: relative;
|
|
151
|
+
width: 100%;
|
|
152
|
+
height: 100%;
|
|
153
|
+
background-color: transparent;
|
|
154
|
+
`;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface Guide {
|
|
2
|
+
id: string;
|
|
3
|
+
position: number;
|
|
4
|
+
orientation: "horizontal" | "vertical";
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface RulerContextType {
|
|
8
|
+
guides: Guide[];
|
|
9
|
+
addGuide: (guide: Omit<Guide, "id">) => void;
|
|
10
|
+
updateGuide: (id: string, position: number) => void;
|
|
11
|
+
removeGuide: (id: string) => void;
|
|
12
|
+
activeGuide: Guide | null;
|
|
13
|
+
setActiveGuide: (guide: Guide | null) => void;
|
|
14
|
+
isDragging: boolean;
|
|
15
|
+
setIsDragging: (dragging: boolean) => void;
|
|
16
|
+
canvasRef: React.RefObject<HTMLDivElement | null>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
import { Select, Page, Field, ScrollArea } from "..";
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: "Components/Select",
|
|
7
|
+
component: Select,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
decorators: [
|
|
10
|
+
(Story) => (
|
|
11
|
+
<Page>
|
|
12
|
+
<Page.Content className="p-medium-30">
|
|
13
|
+
<Story />
|
|
14
|
+
</Page.Content>
|
|
15
|
+
</Page>
|
|
16
|
+
),
|
|
17
|
+
],
|
|
18
|
+
} satisfies Meta<typeof Select>;
|
|
19
|
+
export default meta;
|
|
20
|
+
|
|
21
|
+
type Story = StoryObj<typeof meta>;
|
|
22
|
+
|
|
23
|
+
export const Default: Story = {
|
|
24
|
+
render: ({ ...args }) => {
|
|
25
|
+
const [value, setValue] = React.useState<string | undefined>(undefined);
|
|
26
|
+
const [valueLabel, setValueLabel] = React.useState<string | undefined>(
|
|
27
|
+
undefined,
|
|
28
|
+
);
|
|
29
|
+
const [valueHandle, setValueHandle] = React.useState<string | undefined>(
|
|
30
|
+
undefined,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const TEAM_MEMBERS = [
|
|
34
|
+
{ value: "phoenix", label: "Phoenix Baker", handle: "@phoenix" },
|
|
35
|
+
{ value: "olivia", label: "Olivia Rhye", handle: "@olivia" },
|
|
36
|
+
{ value: "lana", label: "Lana Steiner", handle: "@lana" },
|
|
37
|
+
{ value: "demi", label: "Demi Wilkinson", handle: "@demi" },
|
|
38
|
+
{ value: "candice", label: "Candice Wu", handle: "@candice" },
|
|
39
|
+
{ value: "natali", label: "Natali Craig", handle: "@natali" },
|
|
40
|
+
{ value: "phoenix", label: "Phoenix Baker", handle: "@phoenix" },
|
|
41
|
+
{ value: "olivia", label: "Olivia Rhye", handle: "@olivia" },
|
|
42
|
+
{ value: "lana", label: "Lana Steiner", handle: "@lana" },
|
|
43
|
+
{ value: "demi", label: "Demi Wilkinson", handle: "@demi" },
|
|
44
|
+
{ value: "candice", label: "Candice Wu", handle: "@candice" },
|
|
45
|
+
{ value: "natali", label: "Natali Craig", handle: "@natali" },
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div className="flex flex-column align-center justify-center flex-wrap h-100 w-100 g-medium-30">
|
|
50
|
+
<Field.Wrapper style={{ width: 325 }}>
|
|
51
|
+
<Field.Label>Team member</Field.Label>
|
|
52
|
+
<Select.Root>
|
|
53
|
+
<Select>
|
|
54
|
+
<Select.Trigger>
|
|
55
|
+
{value && valueHandle ? (
|
|
56
|
+
<React.Fragment>
|
|
57
|
+
{valueLabel}
|
|
58
|
+
<span className="opacity-default-60">{valueHandle}</span>
|
|
59
|
+
</React.Fragment>
|
|
60
|
+
) : (
|
|
61
|
+
"Select an Item"
|
|
62
|
+
)}
|
|
63
|
+
</Select.Trigger>
|
|
64
|
+
<Select.Content className="grid g-small-30">
|
|
65
|
+
{TEAM_MEMBERS.map((member) => (
|
|
66
|
+
<Select.Item
|
|
67
|
+
key={member.value}
|
|
68
|
+
value={member.value}
|
|
69
|
+
className="fs-medium-20"
|
|
70
|
+
onClick={() => {
|
|
71
|
+
setValue(member.value);
|
|
72
|
+
setValueHandle(member.handle);
|
|
73
|
+
setValueLabel(member.label);
|
|
74
|
+
}}
|
|
75
|
+
>
|
|
76
|
+
{member.label}
|
|
77
|
+
<span className="opacity-default-60">{member.handle}</span>
|
|
78
|
+
</Select.Item>
|
|
79
|
+
))}
|
|
80
|
+
</Select.Content>
|
|
81
|
+
</Select>
|
|
82
|
+
</Select.Root>
|
|
83
|
+
|
|
84
|
+
<Field.Meta variant="hint">
|
|
85
|
+
This is a hint text to help user.
|
|
86
|
+
</Field.Meta>
|
|
87
|
+
</Field.Wrapper>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
},
|
|
91
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { IReactChildren, IComponentAPI } from "../../../../../types";
|
|
5
|
+
|
|
6
|
+
const defaultComponentAPI: IComponentAPI = {
|
|
7
|
+
id: "",
|
|
8
|
+
states: {},
|
|
9
|
+
methods: {},
|
|
10
|
+
};
|
|
11
|
+
const SelectContext = React.createContext<IComponentAPI>(defaultComponentAPI);
|
|
12
|
+
export const useSelect = () => React.useContext(SelectContext);
|
|
13
|
+
|
|
14
|
+
export const SelectProvider = ({
|
|
15
|
+
children,
|
|
16
|
+
}: IReactChildren): React.JSX.Element => {
|
|
17
|
+
const context = useSelectProvider();
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<SelectContext.Provider value={context}>{children}</SelectContext.Provider>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function useSelectProvider(): IComponentAPI {
|
|
25
|
+
const DEFAULT_POSITIONS = {
|
|
26
|
+
top: 0,
|
|
27
|
+
right: 0,
|
|
28
|
+
bottom: 0,
|
|
29
|
+
left: 0,
|
|
30
|
+
};
|
|
31
|
+
const DEFAULT_DIMENSIONS = {
|
|
32
|
+
width: 0,
|
|
33
|
+
height: 0,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const [open, setOpen] = React.useState<boolean>(false);
|
|
37
|
+
const [value, setValue] = React.useState<string | null>(null);
|
|
38
|
+
const [label, setLabel] = React.useState<string>("");
|
|
39
|
+
|
|
40
|
+
const [contentProps, setContentProps] = React.useState({
|
|
41
|
+
...DEFAULT_POSITIONS,
|
|
42
|
+
...DEFAULT_DIMENSIONS,
|
|
43
|
+
});
|
|
44
|
+
const [triggerProps, setTriggerProps] = React.useState({
|
|
45
|
+
...DEFAULT_POSITIONS,
|
|
46
|
+
...DEFAULT_DIMENSIONS,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const triggerId = React.useId();
|
|
50
|
+
const listboxId = React.useId();
|
|
51
|
+
const composedId = `${triggerId}|${listboxId}`;
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
id: composedId,
|
|
55
|
+
states: {
|
|
56
|
+
open,
|
|
57
|
+
value,
|
|
58
|
+
label,
|
|
59
|
+
contentProps,
|
|
60
|
+
triggerProps,
|
|
61
|
+
},
|
|
62
|
+
methods: {
|
|
63
|
+
toggleOpen: (): boolean | void => setOpen(!open),
|
|
64
|
+
setOpen,
|
|
65
|
+
setValue,
|
|
66
|
+
setLabel,
|
|
67
|
+
setContentProps,
|
|
68
|
+
setTriggerProps,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|