@scripso-homepad/ui 0.4.10 → 0.4.11
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/index.cjs +122 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +124 -45
- package/dist/index.js.map +1 -1
- package/dist/web/index.cjs +45 -33
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.d.cts +2 -1
- package/dist/web/index.d.ts +2 -1
- package/dist/web/index.js +45 -33
- package/dist/web/index.js.map +1 -1
- package/package.json +3 -2
- package/src/components/Calendar.tsx +10 -2
- package/src/components/DatePicker.tsx +3 -3
- package/src/components/Select.tsx +133 -38
- package/src/theme/select.ts +1 -1
- package/src/utils/calendarDays.ts +15 -6
- package/src/web/components/Badge.tsx +1 -1
- package/src/web/components/ConfirmModal.tsx +2 -2
- package/src/web/components/Drawer.stories.tsx +7 -7
- package/src/web/components/Drawer.tsx +2 -2
- package/src/web/components/HistoryTimeline.tsx +2 -2
- package/src/web/components/Modal.stories.tsx +1 -1
- package/src/web/components/Modal.tsx +27 -10
- package/src/web/components/Pagination.tsx +2 -2
- package/src/web/components/TableActionButton.tsx +1 -1
- package/src/web/components/TableActionsMenu.tsx +1 -1
- package/src/web/components/TableIconText.tsx +1 -1
- package/src/web/components/Toaster.stories.tsx +7 -7
- package/src/web/components/table/constants.ts +2 -2
- package/src/web/layout/AppHeader.tsx +1 -1
- package/src/web/layout/BuildingSelect.tsx +2 -2
- package/src/web/layout/DashboardLayout.stories.tsx +1 -1
- package/src/web/layout/SidebarNavItem.tsx +1 -1
- package/src/web/layout/SidebarUserCard.tsx +3 -3
- package/src/web/styles/typography.css +99 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scripso-homepad/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Cross-platform UI components for Homepad (React Web + React Native)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"require": "./dist/web/index.cjs",
|
|
31
31
|
"default": "./src/web/index.ts"
|
|
32
32
|
},
|
|
33
|
-
"./web/table-scroll.css": "./src/web/components/table-scroll.css"
|
|
33
|
+
"./web/table-scroll.css": "./src/web/components/table-scroll.css",
|
|
34
|
+
"./web/typography.css": "./src/web/styles/typography.css"
|
|
34
35
|
},
|
|
35
36
|
"files": [
|
|
36
37
|
"dist",
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
formatCalendarYear,
|
|
14
14
|
getYearPageStart,
|
|
15
15
|
isMonthDisabled,
|
|
16
|
+
isSameDay,
|
|
16
17
|
isYearDisabled,
|
|
17
18
|
selectRangeDate,
|
|
18
19
|
type CalendarDayCell,
|
|
@@ -64,13 +65,13 @@ type CalendarBaseProps = {
|
|
|
64
65
|
export type CalendarSingleProps = CalendarBaseProps & {
|
|
65
66
|
mode?: "single";
|
|
66
67
|
value?: Date;
|
|
67
|
-
onChange?: (date: Date) => void;
|
|
68
|
+
onChange?: (date: Date | undefined) => void;
|
|
68
69
|
};
|
|
69
70
|
|
|
70
71
|
export type CalendarRangeProps = CalendarBaseProps & {
|
|
71
72
|
mode: "range";
|
|
72
73
|
value?: DateRange;
|
|
73
|
-
onChange?: (range: DateRange) => void;
|
|
74
|
+
onChange?: (range: DateRange | undefined) => void;
|
|
74
75
|
};
|
|
75
76
|
|
|
76
77
|
export type CalendarProps = CalendarSingleProps | CalendarRangeProps;
|
|
@@ -275,6 +276,13 @@ export function Calendar(props: CalendarProps) {
|
|
|
275
276
|
return;
|
|
276
277
|
}
|
|
277
278
|
|
|
279
|
+
const currentValue = singleProps?.value;
|
|
280
|
+
|
|
281
|
+
if (currentValue && isSameDay(date, currentValue)) {
|
|
282
|
+
singleProps?.onChange?.(undefined);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
278
286
|
singleProps?.onChange?.(date);
|
|
279
287
|
}
|
|
280
288
|
|
|
@@ -207,7 +207,7 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
207
207
|
});
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
function handleSingleSelect(date: Date) {
|
|
210
|
+
function handleSingleSelect(date: Date | undefined) {
|
|
211
211
|
if (props.mode === "range") {
|
|
212
212
|
return;
|
|
213
213
|
}
|
|
@@ -216,14 +216,14 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
216
216
|
closePicker();
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
function handleRangeSelect(range: DateRange) {
|
|
219
|
+
function handleRangeSelect(range: DateRange | undefined) {
|
|
220
220
|
if (props.mode !== "range") {
|
|
221
221
|
return;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
props.onChange?.(range);
|
|
225
225
|
|
|
226
|
-
if (closeOnRangeComplete && range
|
|
226
|
+
if (closeOnRangeComplete && range?.end) {
|
|
227
227
|
closePicker();
|
|
228
228
|
}
|
|
229
229
|
}
|
|
@@ -73,6 +73,112 @@ function renderSelectIcon(icon: ReactNode, color: string) {
|
|
|
73
73
|
return icon;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
function resolveWebTextNode(
|
|
77
|
+
ref: React.RefObject<ComponentRef<typeof Text> | null>,
|
|
78
|
+
): HTMLElement | null {
|
|
79
|
+
if (Platform.OS !== "web") {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const node = ref.current as unknown;
|
|
84
|
+
return node instanceof HTMLElement ? node : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function syncTruncatedNativeTitle(
|
|
88
|
+
ref: React.RefObject<ComponentRef<typeof Text> | null>,
|
|
89
|
+
label: string,
|
|
90
|
+
): void {
|
|
91
|
+
const element = resolveWebTextNode(ref);
|
|
92
|
+
if (!element) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (element.scrollWidth > element.clientWidth) {
|
|
97
|
+
element.setAttribute("title", label);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
element.removeAttribute("title");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function clearNativeTitle(ref: React.RefObject<ComponentRef<typeof Text> | null>): void {
|
|
105
|
+
resolveWebTextNode(ref)?.removeAttribute("title");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface SelectOptionItemProps {
|
|
109
|
+
option: SelectOption;
|
|
110
|
+
compact: boolean;
|
|
111
|
+
optionHeight: number;
|
|
112
|
+
isSelected: boolean;
|
|
113
|
+
isHighlighted: boolean;
|
|
114
|
+
isLast: boolean;
|
|
115
|
+
selectableIndex: number;
|
|
116
|
+
onHighlight: (selectableIndex: number) => void;
|
|
117
|
+
onSelect: (value: string) => void;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function SelectOptionItem({
|
|
121
|
+
option,
|
|
122
|
+
compact,
|
|
123
|
+
optionHeight,
|
|
124
|
+
isSelected,
|
|
125
|
+
isHighlighted,
|
|
126
|
+
isLast,
|
|
127
|
+
selectableIndex,
|
|
128
|
+
onHighlight,
|
|
129
|
+
onSelect,
|
|
130
|
+
}: SelectOptionItemProps) {
|
|
131
|
+
const labelRef = useRef<ComponentRef<typeof Text>>(null);
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<Pressable
|
|
135
|
+
accessibilityRole="button"
|
|
136
|
+
accessibilityState={{ disabled: option.disabled, selected: isSelected }}
|
|
137
|
+
disabled={option.disabled}
|
|
138
|
+
onPress={() => {
|
|
139
|
+
if (!option.disabled) {
|
|
140
|
+
onSelect(option.value);
|
|
141
|
+
}
|
|
142
|
+
}}
|
|
143
|
+
onHoverIn={() => {
|
|
144
|
+
if (!option.disabled && selectableIndex >= 0) {
|
|
145
|
+
onHighlight(selectableIndex);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
syncTruncatedNativeTitle(labelRef, option.label);
|
|
149
|
+
}}
|
|
150
|
+
onHoverOut={() => {
|
|
151
|
+
clearNativeTitle(labelRef);
|
|
152
|
+
}}
|
|
153
|
+
style={[
|
|
154
|
+
selectDropdownMetrics.option,
|
|
155
|
+
compact && styles.optionCompact,
|
|
156
|
+
{ height: optionHeight, minHeight: optionHeight, maxHeight: optionHeight },
|
|
157
|
+
isSelected && selectDropdownMetrics.optionSelected,
|
|
158
|
+
compact && isSelected && styles.optionSelectedCompact,
|
|
159
|
+
isHighlighted && !isSelected && !option.disabled
|
|
160
|
+
? selectDropdownMetrics.optionHighlighted
|
|
161
|
+
: null,
|
|
162
|
+
!isLast && styles.optionSpacing,
|
|
163
|
+
option.disabled && styles.optionDisabled,
|
|
164
|
+
]}
|
|
165
|
+
>
|
|
166
|
+
<Text
|
|
167
|
+
ref={labelRef}
|
|
168
|
+
style={[
|
|
169
|
+
selectDropdownMetrics.optionLabel,
|
|
170
|
+
styles.optionLabel,
|
|
171
|
+
isSelected && selectDropdownMetrics.optionLabelSelected,
|
|
172
|
+
option.disabled && styles.optionLabelDisabled,
|
|
173
|
+
]}
|
|
174
|
+
numberOfLines={1}
|
|
175
|
+
>
|
|
176
|
+
{option.label}
|
|
177
|
+
</Text>
|
|
178
|
+
</Pressable>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
76
182
|
export type SelectOption = {
|
|
77
183
|
value: string;
|
|
78
184
|
label: string;
|
|
@@ -282,6 +388,12 @@ export function Select({
|
|
|
282
388
|
return;
|
|
283
389
|
}
|
|
284
390
|
|
|
391
|
+
if (selectedValues.includes(nextValue)) {
|
|
392
|
+
(onValueChange as SingleSelectProps["onValueChange"] | undefined)?.("");
|
|
393
|
+
closeMenu();
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
285
397
|
(onValueChange as SingleSelectProps["onValueChange"] | undefined)?.(nextValue);
|
|
286
398
|
closeMenu();
|
|
287
399
|
}
|
|
@@ -293,45 +405,18 @@ export function Select({
|
|
|
293
405
|
const isLast = index === options.length - 1;
|
|
294
406
|
|
|
295
407
|
return (
|
|
296
|
-
<
|
|
408
|
+
<SelectOptionItem
|
|
297
409
|
key={option.value}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
setHighlightedIndex(selectableIndex);
|
|
309
|
-
}
|
|
310
|
-
}}
|
|
311
|
-
style={[
|
|
312
|
-
selectDropdownMetrics.option,
|
|
313
|
-
compact && styles.optionCompact,
|
|
314
|
-
{ height: optionHeight, minHeight: optionHeight, maxHeight: optionHeight },
|
|
315
|
-
isSelected && selectDropdownMetrics.optionSelected,
|
|
316
|
-
compact && isSelected && styles.optionSelectedCompact,
|
|
317
|
-
isHighlighted && !isSelected && !option.disabled
|
|
318
|
-
? selectDropdownMetrics.optionHighlighted
|
|
319
|
-
: null,
|
|
320
|
-
!isLast && styles.optionSpacing,
|
|
321
|
-
option.disabled && styles.optionDisabled,
|
|
322
|
-
]}
|
|
323
|
-
>
|
|
324
|
-
<Text
|
|
325
|
-
style={[
|
|
326
|
-
selectDropdownMetrics.optionLabel,
|
|
327
|
-
isSelected && selectDropdownMetrics.optionLabelSelected,
|
|
328
|
-
option.disabled && styles.optionLabelDisabled,
|
|
329
|
-
]}
|
|
330
|
-
numberOfLines={1}
|
|
331
|
-
>
|
|
332
|
-
{option.label}
|
|
333
|
-
</Text>
|
|
334
|
-
</Pressable>
|
|
410
|
+
option={option}
|
|
411
|
+
compact={compact}
|
|
412
|
+
optionHeight={optionHeight}
|
|
413
|
+
isSelected={isSelected}
|
|
414
|
+
isHighlighted={isHighlighted}
|
|
415
|
+
isLast={isLast}
|
|
416
|
+
selectableIndex={selectableIndex}
|
|
417
|
+
onHighlight={setHighlightedIndex}
|
|
418
|
+
onSelect={handleSelect}
|
|
419
|
+
/>
|
|
335
420
|
);
|
|
336
421
|
});
|
|
337
422
|
|
|
@@ -479,6 +564,16 @@ const styles = StyleSheet.create({
|
|
|
479
564
|
optionDisabled: {
|
|
480
565
|
opacity: 0.5,
|
|
481
566
|
},
|
|
567
|
+
optionLabel: {
|
|
568
|
+
minWidth: 0,
|
|
569
|
+
...(Platform.OS === "web"
|
|
570
|
+
? ({
|
|
571
|
+
overflow: "hidden",
|
|
572
|
+
textOverflow: "ellipsis",
|
|
573
|
+
whiteSpace: "nowrap",
|
|
574
|
+
} as const)
|
|
575
|
+
: null),
|
|
576
|
+
},
|
|
482
577
|
optionLabelDisabled: {
|
|
483
578
|
color: colors.stormGray300,
|
|
484
579
|
},
|
package/src/theme/select.ts
CHANGED
|
@@ -41,24 +41,33 @@ export function isInDateRange(date: Date, start?: Date, end?: Date): boolean {
|
|
|
41
41
|
return !isBeforeDay(day, start) && !isAfterDay(day, end);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export function selectRangeDate(clicked: Date, current?: DateRange): DateRange {
|
|
44
|
+
export function selectRangeDate(clicked: Date, current?: DateRange): DateRange | undefined {
|
|
45
45
|
const clickedDate = stripTime(clicked);
|
|
46
46
|
|
|
47
|
-
if (!current?.start
|
|
47
|
+
if (!current?.start) {
|
|
48
48
|
return { start: clickedDate };
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
const start = stripTime(current.start);
|
|
52
|
+
const end = current.end ? stripTime(current.end) : undefined;
|
|
53
|
+
|
|
54
|
+
if (!end) {
|
|
55
|
+
if (isSameDay(clickedDate, start)) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (isBeforeDay(clickedDate, start)) {
|
|
60
|
+
return { start: clickedDate };
|
|
61
|
+
}
|
|
52
62
|
|
|
53
|
-
if (isSameDay(clickedDate, start)) {
|
|
54
63
|
return { start, end: clickedDate };
|
|
55
64
|
}
|
|
56
65
|
|
|
57
|
-
if (
|
|
58
|
-
return
|
|
66
|
+
if (isSameDay(clickedDate, end)) {
|
|
67
|
+
return undefined;
|
|
59
68
|
}
|
|
60
69
|
|
|
61
|
-
return { start
|
|
70
|
+
return { start: clickedDate };
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
export function isSameDay(left: Date, right: Date): boolean {
|
|
@@ -23,7 +23,7 @@ export function Badge({ children, variant = 'neutral', icon, className }: BadgeP
|
|
|
23
23
|
return (
|
|
24
24
|
<span
|
|
25
25
|
className={cn(
|
|
26
|
-
'inline-flex items-center gap-2 rounded-[28px] px-3 py-1.5
|
|
26
|
+
'inline-flex items-center gap-2 rounded-[28px] px-3 py-1.5 typography-12 font-semibold whitespace-nowrap',
|
|
27
27
|
badgeVariantClasses[variant],
|
|
28
28
|
className,
|
|
29
29
|
)}
|
|
@@ -78,13 +78,13 @@ export function ConfirmModal({
|
|
|
78
78
|
<header className="flex flex-col gap-3 text-center">
|
|
79
79
|
<h2
|
|
80
80
|
id="homepad-confirm-modal-title"
|
|
81
|
-
className="
|
|
81
|
+
className="typography-title tracking-normal text-black"
|
|
82
82
|
>
|
|
83
83
|
{title}
|
|
84
84
|
</h2>
|
|
85
85
|
<div
|
|
86
86
|
id="homepad-confirm-modal-description"
|
|
87
|
-
className="text-center
|
|
87
|
+
className="text-center typography-body tracking-normal text-storm-gray-400"
|
|
88
88
|
>
|
|
89
89
|
{description}
|
|
90
90
|
</div>
|
|
@@ -44,10 +44,10 @@ type Story = StoryObj<typeof meta>;
|
|
|
44
44
|
function DetailField({ label, value }: { label: string; value: string }) {
|
|
45
45
|
return (
|
|
46
46
|
<div className="flex items-center justify-between gap-4">
|
|
47
|
-
<span className="min-w-0 flex-1
|
|
47
|
+
<span className="min-w-0 flex-1 typography-14 font-medium tracking-normal text-storm-gray-500">
|
|
48
48
|
{label}
|
|
49
49
|
</span>
|
|
50
|
-
<span className="shrink-0 text-right
|
|
50
|
+
<span className="shrink-0 text-right typography-14 font-medium leading-[14px] tracking-normal text-storm-gray-900">
|
|
51
51
|
{value}
|
|
52
52
|
</span>
|
|
53
53
|
</div>
|
|
@@ -57,7 +57,7 @@ function DetailField({ label, value }: { label: string; value: string }) {
|
|
|
57
57
|
function DetailsSection({ title, children }: { title: string; children: React.ReactNode }) {
|
|
58
58
|
return (
|
|
59
59
|
<section className="flex flex-col gap-3">
|
|
60
|
-
<h3 className="
|
|
60
|
+
<h3 className="typography-12 font-semibold uppercase tracking-normal text-storm-gray-200">
|
|
61
61
|
{title}
|
|
62
62
|
</h3>
|
|
63
63
|
<div className="flex flex-col gap-4">{children}</div>
|
|
@@ -70,7 +70,7 @@ function RequestDetailsBody() {
|
|
|
70
70
|
<>
|
|
71
71
|
<div className="flex flex-col gap-8 pb-6">
|
|
72
72
|
<div className="flex items-center justify-between gap-4">
|
|
73
|
-
<span className="
|
|
73
|
+
<span className="typography-14 font-medium tracking-normal text-storm-gray-500">
|
|
74
74
|
Կարգավիճակ
|
|
75
75
|
</span>
|
|
76
76
|
<StatusBadge tone="warning">Սպասում է վճարման</StatusBadge>
|
|
@@ -93,7 +93,7 @@ function RequestDetailsBody() {
|
|
|
93
93
|
|
|
94
94
|
<section className="-mx-6 flex flex-col justify-between border-t border-dashed border-storm-gray-50 px-6 pt-6 pb-0">
|
|
95
95
|
<div className="flex flex-col gap-3">
|
|
96
|
-
<h3 className="
|
|
96
|
+
<h3 className="typography-12 font-semibold uppercase tracking-normal text-storm-gray-200">
|
|
97
97
|
Գործողությունների պատմություն
|
|
98
98
|
</h3>
|
|
99
99
|
<HistoryTimeline events={historyEvents} />
|
|
@@ -190,7 +190,7 @@ export const WithoutSubtitle: Story = {
|
|
|
190
190
|
closeAriaLabel="Փակել"
|
|
191
191
|
onClose={args.onClose}
|
|
192
192
|
>
|
|
193
|
-
<p className="
|
|
193
|
+
<p className="typography-14 text-storm-gray-500">
|
|
194
194
|
Drawer content without a subtitle line in the header.
|
|
195
195
|
</p>
|
|
196
196
|
</Drawer>
|
|
@@ -205,7 +205,7 @@ function InteractiveDrawerStory() {
|
|
|
205
205
|
<WebLayoutCanvas className="min-h-screen bg-storm-gray-100 p-6">
|
|
206
206
|
<button
|
|
207
207
|
type="button"
|
|
208
|
-
className="rounded-xl border border-storm-gray-50 bg-white px-4 py-2
|
|
208
|
+
className="rounded-xl border border-storm-gray-50 bg-white px-4 py-2 typography-14 font-medium text-slate-blue"
|
|
209
209
|
onClick={() => setOpen(true)}
|
|
210
210
|
>
|
|
211
211
|
Բացել drawer-ը
|
|
@@ -127,14 +127,14 @@ export function Drawer({
|
|
|
127
127
|
<div className="min-w-0 flex flex-col gap-1">
|
|
128
128
|
<h2
|
|
129
129
|
id="homepad-drawer-title"
|
|
130
|
-
className="
|
|
130
|
+
className="typography-title-sm tracking-normal text-black"
|
|
131
131
|
>
|
|
132
132
|
{title}
|
|
133
133
|
</h2>
|
|
134
134
|
{subtitle ? (
|
|
135
135
|
<p
|
|
136
136
|
id="homepad-drawer-subtitle"
|
|
137
|
-
className="
|
|
137
|
+
className="typography-body tracking-normal text-storm-gray-400"
|
|
138
138
|
>
|
|
139
139
|
{subtitle}
|
|
140
140
|
</p>
|
|
@@ -31,10 +31,10 @@ export function HistoryTimeline({ events }: HistoryTimelineProps) {
|
|
|
31
31
|
</div>
|
|
32
32
|
|
|
33
33
|
<div className="flex min-w-0 flex-1 flex-col gap-2 pt-1.5">
|
|
34
|
-
<p className="
|
|
34
|
+
<p className="typography-14 font-medium tracking-normal text-slate-blue">
|
|
35
35
|
{event.title}
|
|
36
36
|
</p>
|
|
37
|
-
<p className="
|
|
37
|
+
<p className="typography-caption tracking-normal text-storm-gray-300">
|
|
38
38
|
{event.timestamp}
|
|
39
39
|
</p>
|
|
40
40
|
</div>
|
|
@@ -106,7 +106,7 @@ function FooterAlignmentsStory() {
|
|
|
106
106
|
<button
|
|
107
107
|
key={value}
|
|
108
108
|
type="button"
|
|
109
|
-
className="rounded-lg border border-storm-gray-50 px-3 py-1
|
|
109
|
+
className="rounded-lg border border-storm-gray-50 px-3 py-1 typography-14"
|
|
110
110
|
onClick={() => setAlign(value)}
|
|
111
111
|
>
|
|
112
112
|
{value}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect, type ReactNode } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
2
3
|
|
|
3
4
|
import { Button } from '../../components/Button';
|
|
4
5
|
import { cn } from '../utils/cn';
|
|
@@ -22,6 +23,7 @@ export type ModalProps = {
|
|
|
22
23
|
cancelDisabled?: boolean;
|
|
23
24
|
confirmDisabled?: boolean;
|
|
24
25
|
closeOnBackdrop?: boolean;
|
|
26
|
+
closeOnEscape?: boolean;
|
|
25
27
|
containerClassName?: string;
|
|
26
28
|
contentClassName?: string;
|
|
27
29
|
buttonClassName?: string;
|
|
@@ -49,6 +51,7 @@ export function Modal({
|
|
|
49
51
|
cancelDisabled = false,
|
|
50
52
|
confirmDisabled = false,
|
|
51
53
|
closeOnBackdrop = true,
|
|
54
|
+
closeOnEscape = true,
|
|
52
55
|
containerClassName,
|
|
53
56
|
contentClassName,
|
|
54
57
|
buttonClassName,
|
|
@@ -58,8 +61,11 @@ export function Modal({
|
|
|
58
61
|
return;
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
const previousOverflow = document.body.style.overflow;
|
|
65
|
+
document.body.style.overflow = 'hidden';
|
|
66
|
+
|
|
61
67
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
62
|
-
if (event.key === 'Escape') {
|
|
68
|
+
if (event.key === 'Escape' && closeOnEscape) {
|
|
63
69
|
onCancel();
|
|
64
70
|
}
|
|
65
71
|
};
|
|
@@ -67,9 +73,10 @@ export function Modal({
|
|
|
67
73
|
window.addEventListener('keydown', handleKeyDown);
|
|
68
74
|
|
|
69
75
|
return () => {
|
|
76
|
+
document.body.style.overflow = previousOverflow;
|
|
70
77
|
window.removeEventListener('keydown', handleKeyDown);
|
|
71
78
|
};
|
|
72
|
-
}, [open, onCancel]);
|
|
79
|
+
}, [open, onCancel, closeOnEscape]);
|
|
73
80
|
|
|
74
81
|
if (!open) {
|
|
75
82
|
return null;
|
|
@@ -81,10 +88,18 @@ export function Modal({
|
|
|
81
88
|
footerLayout !== 'split' && buttonClassName,
|
|
82
89
|
);
|
|
83
90
|
|
|
84
|
-
|
|
91
|
+
const modal = (
|
|
85
92
|
<div
|
|
86
|
-
className="fixed inset-0 z-
|
|
87
|
-
onClick={
|
|
93
|
+
className="fixed inset-0 z-[100] flex items-center justify-center p-4 backdrop-blur-[12px] bg-storm-gray-850/40"
|
|
94
|
+
onClick={
|
|
95
|
+
closeOnBackdrop
|
|
96
|
+
? (event) => {
|
|
97
|
+
if (event.target === event.currentTarget) {
|
|
98
|
+
onCancel();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
: undefined
|
|
102
|
+
}
|
|
88
103
|
role="presentation"
|
|
89
104
|
>
|
|
90
105
|
<div
|
|
@@ -93,14 +108,14 @@ export function Modal({
|
|
|
93
108
|
aria-labelledby="homepad-modal-title"
|
|
94
109
|
{...(subtitle ? { 'aria-describedby': 'homepad-modal-subtitle' } : {})}
|
|
95
110
|
className={cn(
|
|
96
|
-
'flex w-full max-w-2xl flex-col gap-6 rounded-2xl bg-white p-4 opacity-100 md:p-6',
|
|
111
|
+
'flex max-h-[min(90dvh,calc(100dvh-2rem))] w-full max-w-2xl flex-col gap-6 rounded-2xl bg-white p-4 opacity-100 md:p-6',
|
|
97
112
|
containerClassName,
|
|
98
113
|
)}
|
|
99
114
|
onClick={(event) => {
|
|
100
115
|
event.stopPropagation();
|
|
101
116
|
}}
|
|
102
117
|
>
|
|
103
|
-
<header className={cn('flex', icon ? 'items-center gap-2.5' : undefined)}>
|
|
118
|
+
<header className={cn('flex shrink-0', icon ? 'items-center gap-2.5' : undefined)}>
|
|
104
119
|
{icon ? (
|
|
105
120
|
<div
|
|
106
121
|
className={cn(
|
|
@@ -116,14 +131,14 @@ export function Modal({
|
|
|
116
131
|
<div className="flex min-w-0 flex-col gap-2">
|
|
117
132
|
<h2
|
|
118
133
|
id="homepad-modal-title"
|
|
119
|
-
className="
|
|
134
|
+
className="typography-title-sm tracking-normal text-black"
|
|
120
135
|
>
|
|
121
136
|
{title}
|
|
122
137
|
</h2>
|
|
123
138
|
{subtitle ? (
|
|
124
139
|
<p
|
|
125
140
|
id="homepad-modal-subtitle"
|
|
126
|
-
className="
|
|
141
|
+
className="typography-caption tracking-normal text-storm-gray-400"
|
|
127
142
|
>
|
|
128
143
|
{subtitle}
|
|
129
144
|
</p>
|
|
@@ -131,7 +146,7 @@ export function Modal({
|
|
|
131
146
|
</div>
|
|
132
147
|
</header>
|
|
133
148
|
|
|
134
|
-
<div className={cn('min-w-0', contentClassName)}>{children}</div>
|
|
149
|
+
<div className={cn('min-h-0 min-w-0 flex-1 overflow-y-auto', contentClassName)}>{children}</div>
|
|
135
150
|
|
|
136
151
|
<footer
|
|
137
152
|
className={cn(
|
|
@@ -159,4 +174,6 @@ export function Modal({
|
|
|
159
174
|
</div>
|
|
160
175
|
</div>
|
|
161
176
|
);
|
|
177
|
+
|
|
178
|
+
return typeof document !== 'undefined' ? createPortal(modal, document.body) : modal;
|
|
162
179
|
}
|
|
@@ -23,7 +23,7 @@ const defaultLabels: PaginationLabels = {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
const paginationControlClassName =
|
|
26
|
-
'inline-flex size-7 items-center justify-center rounded-lg border border-storm-gray-50 p-1
|
|
26
|
+
'inline-flex size-7 items-center justify-center rounded-lg border border-storm-gray-50 p-1 typography-12 font-medium text-storm-gray-500 transition-colors';
|
|
27
27
|
|
|
28
28
|
const paginationHoverClassName = 'cursor-pointer hover:bg-storm-gray-0';
|
|
29
29
|
|
|
@@ -78,7 +78,7 @@ export function Pagination({
|
|
|
78
78
|
className,
|
|
79
79
|
)}
|
|
80
80
|
>
|
|
81
|
-
<p className="
|
|
81
|
+
<p className="typography-12 font-medium text-storm-gray-200">
|
|
82
82
|
{mergedLabels.showing({ start, end, total })}
|
|
83
83
|
</p>
|
|
84
84
|
|
|
@@ -17,7 +17,7 @@ export type TableActionButtonProps = {
|
|
|
17
17
|
>;
|
|
18
18
|
|
|
19
19
|
const inlineActionClassName =
|
|
20
|
-
'inline-flex cursor-pointer items-center gap-1 rounded-lg border border-storm-gray-50 bg-white px-3 py-2
|
|
20
|
+
'inline-flex cursor-pointer items-center gap-1 rounded-lg border border-storm-gray-50 bg-white px-3 py-2 typography-12 font-medium text-storm-gray-900 transition-colors hover:bg-storm-gray-0';
|
|
21
21
|
|
|
22
22
|
const variantClasses: Record<TableActionButtonVariant, string> = {
|
|
23
23
|
approve: inlineActionClassName,
|
|
@@ -172,7 +172,7 @@ export function TableActionsMenu({
|
|
|
172
172
|
item.onSelect();
|
|
173
173
|
}}
|
|
174
174
|
className={cn(
|
|
175
|
-
'flex h-[51px] w-full cursor-pointer items-center gap-2.5 px-3 text-left
|
|
175
|
+
'flex h-[51px] w-full cursor-pointer items-center gap-2.5 px-3 text-left typography-14 font-medium text-storm-gray-900 transition-colors hover:bg-storm-gray-0',
|
|
176
176
|
index > 0 && 'border-t border-storm-gray-50',
|
|
177
177
|
item.disabled && 'cursor-not-allowed opacity-50',
|
|
178
178
|
)}
|
|
@@ -12,7 +12,7 @@ export function TableIconText({ icon, children, className }: TableIconTextProps)
|
|
|
12
12
|
return (
|
|
13
13
|
<span
|
|
14
14
|
className={cn(
|
|
15
|
-
'inline-flex items-center gap-2
|
|
15
|
+
'inline-flex items-center gap-2 typography-14 font-medium tracking-normal text-storm-gray-900',
|
|
16
16
|
className,
|
|
17
17
|
)}
|
|
18
18
|
>
|