@scripso-homepad/ui 0.4.7 → 0.4.9
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/README.md +1 -1
- package/dist/{chunk-7KLVMNDM.js → chunk-YSDLHEJ7.js} +218 -28
- package/dist/chunk-YSDLHEJ7.js.map +1 -0
- package/dist/index.cjs +24 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -29
- package/dist/index.d.ts +29 -29
- package/dist/index.js +63 -249
- package/dist/index.js.map +1 -1
- package/dist/web/index.cjs +482 -21
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.d.cts +43 -3
- package/dist/web/index.d.ts +43 -3
- package/dist/web/index.js +249 -6
- package/dist/web/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Button.tsx +10 -3
- package/src/components/Select.tsx +1 -1
- package/src/icons/ArrowUpRightIcon.tsx +1 -1
- package/src/icons/ArrowUpRightIcon.web.tsx +1 -1
- package/src/theme/input.ts +2 -2
- package/src/theme/select.ts +2 -2
- package/src/theme/tokens.ts +12 -12
- package/src/web/components/ConfirmModal.stories.tsx +55 -0
- package/src/web/components/ConfirmModal.tsx +116 -0
- package/src/web/components/Modal.stories.tsx +130 -0
- package/src/web/components/Modal.tsx +162 -0
- package/src/web/components/TableActionButton.tsx +6 -1
- package/src/web/components/TableActionsMenu.tsx +2 -2
- package/src/web/icons/TableMenuInfoIcon.tsx +2 -2
- package/src/web/index.ts +6 -0
- package/dist/chunk-7KLVMNDM.js.map +0 -1
package/package.json
CHANGED
|
@@ -115,14 +115,21 @@ export function Button({
|
|
|
115
115
|
const preset = variantConfig[variant];
|
|
116
116
|
const metrics = sizeConfig[size];
|
|
117
117
|
|
|
118
|
-
useApplyWebClassName(containerRef, className);
|
|
119
|
-
useApplyWebClassName(textRef, textClassName);
|
|
120
|
-
|
|
121
118
|
const label = typeof children !== "undefined" ? children : title;
|
|
122
119
|
const hasCustomChildren = typeof children !== "undefined";
|
|
123
120
|
const customIcon =
|
|
124
121
|
icon != null && typeof icon !== "boolean" ? icon : undefined;
|
|
125
122
|
const hasIcon = showIcon || icon === true || customIcon != null;
|
|
123
|
+
const resolvedContainerClassName = [
|
|
124
|
+
Platform.OS === "web" ? "max-md:px-4!" : "",
|
|
125
|
+
className,
|
|
126
|
+
]
|
|
127
|
+
.filter(Boolean)
|
|
128
|
+
.join(" ");
|
|
129
|
+
|
|
130
|
+
useApplyWebClassName(containerRef, resolvedContainerClassName || undefined);
|
|
131
|
+
useApplyWebClassName(textRef, textClassName);
|
|
132
|
+
|
|
126
133
|
const iconNode =
|
|
127
134
|
customIcon ?? (
|
|
128
135
|
<ArrowUpRightIcon size={metrics.iconSize} color={preset.iconColor} />
|
|
@@ -176,7 +176,7 @@ export function Select({
|
|
|
176
176
|
() =>
|
|
177
177
|
resolveSelectDropdownContentHeight(options.length, optionHeight) >
|
|
178
178
|
SELECT_DROPDOWN_MAX_HEIGHT - SELECT_DROPDOWN_PADDING * 2,
|
|
179
|
-
[
|
|
179
|
+
[optionHeight, options.length],
|
|
180
180
|
);
|
|
181
181
|
|
|
182
182
|
useApplyWebClassName(wrapperRef, className);
|
package/src/theme/input.ts
CHANGED
|
@@ -53,8 +53,8 @@ function createOutlineStyle(ringColor: string): ViewStyle {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
const defaultOutline: ViewStyle = {
|
|
56
|
-
borderRadius: radii.lg,
|
|
57
|
-
borderWidth:
|
|
56
|
+
borderRadius: radii.lg + INPUT_OUTLINE_WIDTH,
|
|
57
|
+
borderWidth: INPUT_OUTLINE_WIDTH,
|
|
58
58
|
borderColor: colors.transparent,
|
|
59
59
|
padding: 0,
|
|
60
60
|
backgroundColor: colors.transparent,
|
package/src/theme/select.ts
CHANGED
|
@@ -67,8 +67,8 @@ function createOutlineStyle(ringColor: string): ViewStyle {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
const defaultOutline: ViewStyle = {
|
|
70
|
-
borderRadius: radii.lg,
|
|
71
|
-
borderWidth:
|
|
70
|
+
borderRadius: radii.lg + INPUT_OUTLINE_WIDTH,
|
|
71
|
+
borderWidth: INPUT_OUTLINE_WIDTH,
|
|
72
72
|
borderColor: colors.transparent,
|
|
73
73
|
padding: 0,
|
|
74
74
|
backgroundColor: colors.transparent,
|
package/src/theme/tokens.ts
CHANGED
|
@@ -23,18 +23,18 @@ export type StormGrayStep = keyof typeof stormGray;
|
|
|
23
23
|
|
|
24
24
|
/** Navy scale — Figma steps 0 → 8.5 */
|
|
25
25
|
export const navy = {
|
|
26
|
-
"0": "#
|
|
27
|
-
"0.5": "#
|
|
28
|
-
"1": "#
|
|
29
|
-
"1.5": "#
|
|
30
|
-
"2": "#
|
|
31
|
-
"3": "#
|
|
32
|
-
"4": "#
|
|
33
|
-
"5": "#
|
|
34
|
-
"6": "#
|
|
35
|
-
"7": "#
|
|
36
|
-
"8": "#
|
|
37
|
-
"8.5": "#
|
|
26
|
+
"0": "#f6fafe",
|
|
27
|
+
"0.5": "#dee5eb",
|
|
28
|
+
"1": "#c6d0d8",
|
|
29
|
+
"1.5": "#afbac5",
|
|
30
|
+
"2": "#97a5b2",
|
|
31
|
+
"3": "#677b8c",
|
|
32
|
+
"4": "#385066",
|
|
33
|
+
"5": "#082640",
|
|
34
|
+
"6": "#061e33",
|
|
35
|
+
"7": "#051726",
|
|
36
|
+
"8": "#030f1a",
|
|
37
|
+
"8.5": "#020b13",
|
|
38
38
|
} as const;
|
|
39
39
|
|
|
40
40
|
export type NavyStep = keyof typeof navy;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { fn } from '@storybook/test';
|
|
3
|
+
|
|
4
|
+
import { WebLayoutCanvas } from '../layout/story-helpers';
|
|
5
|
+
|
|
6
|
+
import { ConfirmModal } from './ConfirmModal';
|
|
7
|
+
|
|
8
|
+
const meta = {
|
|
9
|
+
title: 'Web Components/ConfirmModal',
|
|
10
|
+
component: ConfirmModal,
|
|
11
|
+
parameters: {
|
|
12
|
+
layout: 'fullscreen',
|
|
13
|
+
webLayout: true,
|
|
14
|
+
},
|
|
15
|
+
args: {
|
|
16
|
+
open: true,
|
|
17
|
+
title: 'Կասեցնե՞լ շենքի մուտքը',
|
|
18
|
+
description: (
|
|
19
|
+
<>
|
|
20
|
+
Դուք պատրաստվում եք կասեցնել «Կենտրոն» շենքի մուտքը։
|
|
21
|
+
<br />
|
|
22
|
+
Բոլոր օգտատերերը կկորցնեն հասանելիությունը համակարգին։
|
|
23
|
+
</>
|
|
24
|
+
),
|
|
25
|
+
cancelText: 'Չեղարկել',
|
|
26
|
+
confirmText: 'Կասեցնել',
|
|
27
|
+
onCancel: fn(),
|
|
28
|
+
onConfirm: fn(),
|
|
29
|
+
},
|
|
30
|
+
} satisfies Meta<typeof ConfirmModal>;
|
|
31
|
+
|
|
32
|
+
export default meta;
|
|
33
|
+
type Story = StoryObj<typeof meta>;
|
|
34
|
+
|
|
35
|
+
export const Default: Story = {
|
|
36
|
+
render: (args) => (
|
|
37
|
+
<WebLayoutCanvas className="min-h-screen bg-storm-gray-50 p-6">
|
|
38
|
+
<ConfirmModal {...args} />
|
|
39
|
+
</WebLayoutCanvas>
|
|
40
|
+
),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const RejectRequest: Story = {
|
|
44
|
+
args: {
|
|
45
|
+
title: 'Մերժե՞լ շենքի հայտը',
|
|
46
|
+
description:' Դուք պատրաստվում եք մերժել «Արաբկիր 12» հասցեի շենքի հայտը։ Կազմակերպությունը կտեղեկացվի մերժման մասին։',
|
|
47
|
+
cancelText: 'Չեղարկել',
|
|
48
|
+
confirmText: 'Մերժել',
|
|
49
|
+
},
|
|
50
|
+
render: (args) => (
|
|
51
|
+
<WebLayoutCanvas className="min-h-screen bg-storm-gray-50 p-6">
|
|
52
|
+
<ConfirmModal {...args} />
|
|
53
|
+
</WebLayoutCanvas>
|
|
54
|
+
),
|
|
55
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { useEffect, type ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Button } from '../../components/Button';
|
|
4
|
+
import { cn } from '../utils/cn';
|
|
5
|
+
|
|
6
|
+
export type ConfirmModalProps = {
|
|
7
|
+
open: boolean;
|
|
8
|
+
title: string;
|
|
9
|
+
description: ReactNode;
|
|
10
|
+
cancelText: string;
|
|
11
|
+
confirmText: string;
|
|
12
|
+
onCancel: () => void;
|
|
13
|
+
onConfirm: () => void;
|
|
14
|
+
cancelDisabled?: boolean;
|
|
15
|
+
confirmDisabled?: boolean;
|
|
16
|
+
closeOnBackdrop?: boolean;
|
|
17
|
+
containerClassName?: string;
|
|
18
|
+
buttonClassName?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function ConfirmModal({
|
|
22
|
+
open,
|
|
23
|
+
title,
|
|
24
|
+
description,
|
|
25
|
+
cancelText,
|
|
26
|
+
confirmText,
|
|
27
|
+
onCancel,
|
|
28
|
+
onConfirm,
|
|
29
|
+
cancelDisabled = false,
|
|
30
|
+
confirmDisabled = false,
|
|
31
|
+
closeOnBackdrop = true,
|
|
32
|
+
containerClassName,
|
|
33
|
+
buttonClassName,
|
|
34
|
+
}: ConfirmModalProps) {
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (!open) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
41
|
+
if (event.key === 'Escape') {
|
|
42
|
+
onCancel();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
47
|
+
|
|
48
|
+
return () => {
|
|
49
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
50
|
+
};
|
|
51
|
+
}, [open, onCancel]);
|
|
52
|
+
|
|
53
|
+
if (!open) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const sharedButtonClassName = cn('btn h-10 min-w-0 flex-1 justify-center!', buttonClassName);
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
className="fixed inset-0 z-50 flex items-center justify-center p-4 backdrop-blur-md bg-storm-gray-850/40"
|
|
62
|
+
onClick={closeOnBackdrop ? onCancel : undefined}
|
|
63
|
+
role="presentation"
|
|
64
|
+
>
|
|
65
|
+
<div
|
|
66
|
+
role="dialog"
|
|
67
|
+
aria-modal="true"
|
|
68
|
+
aria-labelledby="homepad-confirm-modal-title"
|
|
69
|
+
aria-describedby="homepad-confirm-modal-description"
|
|
70
|
+
className={cn(
|
|
71
|
+
'flex w-full max-w-md flex-col gap-8 rounded-2xl bg-white p-4 opacity-100 md:p-6',
|
|
72
|
+
containerClassName,
|
|
73
|
+
)}
|
|
74
|
+
onClick={(event) => {
|
|
75
|
+
event.stopPropagation();
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
<header className="flex flex-col gap-3 text-center">
|
|
79
|
+
<h2
|
|
80
|
+
id="homepad-confirm-modal-title"
|
|
81
|
+
className="font-sans text-xl font-bold leading-6.75 tracking-normal text-black"
|
|
82
|
+
>
|
|
83
|
+
{title}
|
|
84
|
+
</h2>
|
|
85
|
+
<div
|
|
86
|
+
id="homepad-confirm-modal-description"
|
|
87
|
+
className="text-center font-sans text-sm font-normal leading-4.75 tracking-normal text-storm-gray-400"
|
|
88
|
+
>
|
|
89
|
+
{description}
|
|
90
|
+
</div>
|
|
91
|
+
</header>
|
|
92
|
+
|
|
93
|
+
<footer className="flex w-full gap-4">
|
|
94
|
+
<Button
|
|
95
|
+
title={cancelText}
|
|
96
|
+
onPress={onCancel}
|
|
97
|
+
variant="gray"
|
|
98
|
+
size='sm'
|
|
99
|
+
disabled={cancelDisabled}
|
|
100
|
+
className={sharedButtonClassName}
|
|
101
|
+
textClassName="text-center!"
|
|
102
|
+
/>
|
|
103
|
+
<Button
|
|
104
|
+
title={confirmText}
|
|
105
|
+
onPress={onConfirm}
|
|
106
|
+
variant="primary"
|
|
107
|
+
size='sm'
|
|
108
|
+
disabled={confirmDisabled}
|
|
109
|
+
className={sharedButtonClassName}
|
|
110
|
+
textClassName="text-center!"
|
|
111
|
+
/>
|
|
112
|
+
</footer>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { fn } from '@storybook/test';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import { Input } from '../../components/Input';
|
|
6
|
+
import { TableMenuEditIcon } from '../icons/TableMenuEditIcon';
|
|
7
|
+
import { WebLayoutCanvas } from '../layout/story-helpers';
|
|
8
|
+
|
|
9
|
+
import { Modal, type ModalFooterAlign } from './Modal';
|
|
10
|
+
|
|
11
|
+
const meta = {
|
|
12
|
+
title: 'Web Components/Modal',
|
|
13
|
+
component: Modal,
|
|
14
|
+
parameters: {
|
|
15
|
+
layout: 'fullscreen',
|
|
16
|
+
webLayout: true,
|
|
17
|
+
},
|
|
18
|
+
args: {
|
|
19
|
+
open: true,
|
|
20
|
+
title: 'Նոր շենքի ավելացում',
|
|
21
|
+
subtitle: 'Լրացրեք տվյալները՝ նոր շենք ավելացնելու համար',
|
|
22
|
+
cancelText: 'Չեղարկել',
|
|
23
|
+
confirmText: 'Ավելացնել շենքը',
|
|
24
|
+
onCancel: fn(),
|
|
25
|
+
onConfirm: fn(),
|
|
26
|
+
footerAlign: 'right' as ModalFooterAlign,
|
|
27
|
+
},
|
|
28
|
+
} satisfies Meta<typeof Modal>;
|
|
29
|
+
|
|
30
|
+
export default meta;
|
|
31
|
+
type Story = StoryObj<typeof meta>;
|
|
32
|
+
|
|
33
|
+
function AddBuildingModalBody() {
|
|
34
|
+
return (
|
|
35
|
+
<div className="flex flex-col gap-4">
|
|
36
|
+
<div className="grid gap-4 md:grid-cols-2">
|
|
37
|
+
<Input label="Կազմակերպություն" value="" onChangeText={() => undefined} className="w-full" />
|
|
38
|
+
<Input label="Հասցե" value="" onChangeText={() => undefined} className="w-full" />
|
|
39
|
+
</div>
|
|
40
|
+
<Input label="Կոնտակտային անձ" value="" onChangeText={() => undefined} className="w-full" />
|
|
41
|
+
<div className="grid gap-4 md:grid-cols-2">
|
|
42
|
+
<Input label="Էլ. փոստ" value="" onChangeText={() => undefined} className="w-full" />
|
|
43
|
+
<Input label="Հեռախոսահամար" value="" onChangeText={() => undefined} className="w-full" />
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const Default: Story = {
|
|
50
|
+
args: {
|
|
51
|
+
children: null,
|
|
52
|
+
},
|
|
53
|
+
render: (args) => (
|
|
54
|
+
<WebLayoutCanvas className="min-h-screen bg-storm-gray-50 p-6">
|
|
55
|
+
<Modal {...args}>
|
|
56
|
+
<AddBuildingModalBody />
|
|
57
|
+
</Modal>
|
|
58
|
+
</WebLayoutCanvas>
|
|
59
|
+
),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const WithIcon: Story = {
|
|
63
|
+
args: {
|
|
64
|
+
children: null,
|
|
65
|
+
title: 'Շենքի խմբագրում',
|
|
66
|
+
subtitle: 'Թարմացրեք շենքի տվյալները',
|
|
67
|
+
icon: <TableMenuEditIcon />,
|
|
68
|
+
},
|
|
69
|
+
render: (args) => (
|
|
70
|
+
<WebLayoutCanvas className="min-h-screen bg-storm-gray-50 p-6">
|
|
71
|
+
<Modal {...args}>
|
|
72
|
+
<AddBuildingModalBody />
|
|
73
|
+
</Modal>
|
|
74
|
+
</WebLayoutCanvas>
|
|
75
|
+
),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const SplitFooter: Story = {
|
|
79
|
+
args: {
|
|
80
|
+
children: null,
|
|
81
|
+
footerLayout: 'split',
|
|
82
|
+
},
|
|
83
|
+
render: (args) => (
|
|
84
|
+
<WebLayoutCanvas className="min-h-screen bg-storm-gray-50 p-6">
|
|
85
|
+
<Modal {...args}>
|
|
86
|
+
<AddBuildingModalBody />
|
|
87
|
+
</Modal>
|
|
88
|
+
</WebLayoutCanvas>
|
|
89
|
+
),
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const FooterAlignments: Story = {
|
|
93
|
+
args: {
|
|
94
|
+
children: null,
|
|
95
|
+
},
|
|
96
|
+
render: () => <FooterAlignmentsStory />,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
function FooterAlignmentsStory() {
|
|
100
|
+
const [align, setAlign] = useState<ModalFooterAlign>('right');
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<WebLayoutCanvas className="min-h-screen bg-storm-gray-50 p-6">
|
|
104
|
+
<div className="mb-4 flex gap-2">
|
|
105
|
+
{(['left', 'center', 'right'] as const).map((value) => (
|
|
106
|
+
<button
|
|
107
|
+
key={value}
|
|
108
|
+
type="button"
|
|
109
|
+
className="rounded-lg border border-storm-gray-50 px-3 py-1 text-sm"
|
|
110
|
+
onClick={() => setAlign(value)}
|
|
111
|
+
>
|
|
112
|
+
{value}
|
|
113
|
+
</button>
|
|
114
|
+
))}
|
|
115
|
+
</div>
|
|
116
|
+
<Modal
|
|
117
|
+
open
|
|
118
|
+
title="Նոր շենքի ավելացում"
|
|
119
|
+
subtitle="Լրացրեք տվյալները՝ նոր շենք ավելացնելու համար"
|
|
120
|
+
cancelText="Չեղարկել"
|
|
121
|
+
confirmText="Ավելացնել շենքը"
|
|
122
|
+
footerAlign={align}
|
|
123
|
+
onCancel={fn()}
|
|
124
|
+
onConfirm={fn()}
|
|
125
|
+
>
|
|
126
|
+
<AddBuildingModalBody />
|
|
127
|
+
</Modal>
|
|
128
|
+
</WebLayoutCanvas>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { useEffect, type ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Button } from '../../components/Button';
|
|
4
|
+
import { cn } from '../utils/cn';
|
|
5
|
+
|
|
6
|
+
export type ModalFooterAlign = 'left' | 'center' | 'right';
|
|
7
|
+
export type ModalFooterLayout = 'inline' | 'split';
|
|
8
|
+
|
|
9
|
+
export type ModalProps = {
|
|
10
|
+
open: boolean;
|
|
11
|
+
title: string;
|
|
12
|
+
subtitle?: string;
|
|
13
|
+
icon?: ReactNode;
|
|
14
|
+
iconContainerClassName?: string;
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
cancelText: string;
|
|
17
|
+
confirmText: string;
|
|
18
|
+
onCancel: () => void;
|
|
19
|
+
onConfirm: () => void;
|
|
20
|
+
footerAlign?: ModalFooterAlign;
|
|
21
|
+
footerLayout?: ModalFooterLayout;
|
|
22
|
+
cancelDisabled?: boolean;
|
|
23
|
+
confirmDisabled?: boolean;
|
|
24
|
+
closeOnBackdrop?: boolean;
|
|
25
|
+
containerClassName?: string;
|
|
26
|
+
contentClassName?: string;
|
|
27
|
+
buttonClassName?: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const footerAlignClasses: Record<ModalFooterAlign, string> = {
|
|
31
|
+
left: 'justify-start',
|
|
32
|
+
center: 'justify-center',
|
|
33
|
+
right: 'justify-end',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function Modal({
|
|
37
|
+
open,
|
|
38
|
+
title,
|
|
39
|
+
subtitle,
|
|
40
|
+
icon,
|
|
41
|
+
iconContainerClassName,
|
|
42
|
+
children,
|
|
43
|
+
cancelText,
|
|
44
|
+
confirmText,
|
|
45
|
+
onCancel,
|
|
46
|
+
onConfirm,
|
|
47
|
+
footerAlign = 'right',
|
|
48
|
+
footerLayout = 'inline',
|
|
49
|
+
cancelDisabled = false,
|
|
50
|
+
confirmDisabled = false,
|
|
51
|
+
closeOnBackdrop = true,
|
|
52
|
+
containerClassName,
|
|
53
|
+
contentClassName,
|
|
54
|
+
buttonClassName,
|
|
55
|
+
}: ModalProps) {
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!open) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
62
|
+
if (event.key === 'Escape') {
|
|
63
|
+
onCancel();
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
68
|
+
|
|
69
|
+
return () => {
|
|
70
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
71
|
+
};
|
|
72
|
+
}, [open, onCancel]);
|
|
73
|
+
|
|
74
|
+
if (!open) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const sharedButtonClassName = cn(
|
|
79
|
+
'btn h-13 justify-center!',
|
|
80
|
+
footerLayout === 'split' && 'min-w-0 flex-1',
|
|
81
|
+
buttonClassName,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div
|
|
86
|
+
className="fixed inset-0 z-50 flex items-center justify-center p-4 backdrop-blur-[12px] bg-storm-gray-850/40"
|
|
87
|
+
onClick={closeOnBackdrop ? onCancel : undefined}
|
|
88
|
+
role="presentation"
|
|
89
|
+
>
|
|
90
|
+
<div
|
|
91
|
+
role="dialog"
|
|
92
|
+
aria-modal="true"
|
|
93
|
+
aria-labelledby="homepad-modal-title"
|
|
94
|
+
{...(subtitle ? { 'aria-describedby': 'homepad-modal-subtitle' } : {})}
|
|
95
|
+
className={cn(
|
|
96
|
+
'flex w-full max-w-2xl flex-col gap-6 rounded-2xl bg-white p-4 opacity-100 md:p-6',
|
|
97
|
+
containerClassName,
|
|
98
|
+
)}
|
|
99
|
+
onClick={(event) => {
|
|
100
|
+
event.stopPropagation();
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
<header className={cn('flex', icon ? 'items-center gap-2.5' : undefined)}>
|
|
104
|
+
{icon ? (
|
|
105
|
+
<div
|
|
106
|
+
className={cn(
|
|
107
|
+
'flex size-11 shrink-0 items-center justify-center rounded-lg bg-storm-gray-50 p-3 opacity-100',
|
|
108
|
+
iconContainerClassName,
|
|
109
|
+
)}
|
|
110
|
+
>
|
|
111
|
+
<div className="flex size-5 shrink-0 items-center justify-center [&>*]:size-5 [&_svg]:size-5">
|
|
112
|
+
{icon}
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
) : null}
|
|
116
|
+
<div className="flex min-w-0 flex-col gap-2">
|
|
117
|
+
<h2
|
|
118
|
+
id="homepad-modal-title"
|
|
119
|
+
className="font-sans text-lg font-bold leading-none tracking-normal text-black"
|
|
120
|
+
>
|
|
121
|
+
{title}
|
|
122
|
+
</h2>
|
|
123
|
+
{subtitle ? (
|
|
124
|
+
<p
|
|
125
|
+
id="homepad-modal-subtitle"
|
|
126
|
+
className="font-sans text-xs font-normal leading-none tracking-normal text-storm-gray-400"
|
|
127
|
+
>
|
|
128
|
+
{subtitle}
|
|
129
|
+
</p>
|
|
130
|
+
) : null}
|
|
131
|
+
</div>
|
|
132
|
+
</header>
|
|
133
|
+
|
|
134
|
+
<div className={cn('min-w-0', contentClassName)}>{children}</div>
|
|
135
|
+
|
|
136
|
+
<footer
|
|
137
|
+
className={cn(
|
|
138
|
+
'flex gap-4',
|
|
139
|
+
footerLayout === 'split' ? 'w-full' : footerAlignClasses[footerAlign],
|
|
140
|
+
)}
|
|
141
|
+
>
|
|
142
|
+
<Button
|
|
143
|
+
title={cancelText}
|
|
144
|
+
onPress={onCancel}
|
|
145
|
+
variant="gray"
|
|
146
|
+
disabled={cancelDisabled}
|
|
147
|
+
className={sharedButtonClassName}
|
|
148
|
+
textClassName="text-center!"
|
|
149
|
+
/>
|
|
150
|
+
<Button
|
|
151
|
+
title={confirmText}
|
|
152
|
+
onPress={onConfirm}
|
|
153
|
+
variant="primary"
|
|
154
|
+
disabled={confirmDisabled}
|
|
155
|
+
className={sharedButtonClassName}
|
|
156
|
+
textClassName="text-center!"
|
|
157
|
+
/>
|
|
158
|
+
</footer>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
@@ -11,7 +11,10 @@ export type TableActionButtonProps = {
|
|
|
11
11
|
ariaLabel?: string;
|
|
12
12
|
className?: string;
|
|
13
13
|
disabled?: boolean;
|
|
14
|
-
} & Pick<
|
|
14
|
+
} & Pick<
|
|
15
|
+
ButtonHTMLAttributes<HTMLButtonElement>,
|
|
16
|
+
'onClick' | 'title' | 'aria-expanded' | 'aria-haspopup' | 'aria-controls'
|
|
17
|
+
>;
|
|
15
18
|
|
|
16
19
|
const actionButtonClassName =
|
|
17
20
|
'inline-flex cursor-pointer items-center gap-1 rounded-lg border border-storm-gray-50 bg-white px-3 py-2 font-sans text-xs font-medium leading-none text-storm-gray-900 transition-colors hover:bg-storm-gray-0';
|
|
@@ -40,6 +43,7 @@ export const TableActionButton = forwardRef<HTMLButtonElement, TableActionButton
|
|
|
40
43
|
ref,
|
|
41
44
|
) {
|
|
42
45
|
const isInlineAction = variant === 'approve' || variant === 'reject';
|
|
46
|
+
const hasLabel = Boolean(children);
|
|
43
47
|
|
|
44
48
|
return (
|
|
45
49
|
<button
|
|
@@ -50,6 +54,7 @@ export const TableActionButton = forwardRef<HTMLButtonElement, TableActionButton
|
|
|
50
54
|
aria-label={ariaLabel}
|
|
51
55
|
className={cn(
|
|
52
56
|
variantClasses[variant],
|
|
57
|
+
isInlineAction && !hasLabel && 'px-2',
|
|
53
58
|
disabled && 'cursor-not-allowed opacity-50',
|
|
54
59
|
className,
|
|
55
60
|
)}
|
|
@@ -172,13 +172,13 @@ export function TableActionsMenu({
|
|
|
172
172
|
item.onSelect();
|
|
173
173
|
}}
|
|
174
174
|
className={cn(
|
|
175
|
-
'flex w-full cursor-pointer items-center gap-2.5 px-3
|
|
175
|
+
'flex h-[51px] w-full cursor-pointer items-center gap-2.5 px-3 text-left font-sans text-sm font-medium leading-none 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
|
)}
|
|
179
179
|
>
|
|
180
180
|
{icon ? (
|
|
181
|
-
<span className="inline-flex size-5 shrink-0 items-center justify-center">
|
|
181
|
+
<span className="inline-flex size-5 shrink-0 items-center justify-center text-storm-gray-200">
|
|
182
182
|
{icon}
|
|
183
183
|
</span>
|
|
184
184
|
) : null}
|
|
@@ -16,8 +16,8 @@ export function TableMenuInfoIcon({ className }: TableMenuInfoIconProps) {
|
|
|
16
16
|
className={cn('shrink-0', className)}
|
|
17
17
|
>
|
|
18
18
|
<path
|
|
19
|
-
d="
|
|
20
|
-
stroke="
|
|
19
|
+
d="M10.0013 13.3337V10.0003M10.0013 6.66699H10.0096M18.3346 10.0003C18.3346 14.6027 14.6037 18.3337 10.0013 18.3337C5.39893 18.3337 1.66797 14.6027 1.66797 10.0003C1.66797 5.39795 5.39893 1.66699 10.0013 1.66699C14.6037 1.66699 18.3346 5.39795 18.3346 10.0003Z"
|
|
20
|
+
stroke="currentColor"
|
|
21
21
|
strokeWidth="2"
|
|
22
22
|
strokeLinecap="round"
|
|
23
23
|
strokeLinejoin="round"
|
package/src/web/index.ts
CHANGED
|
@@ -96,6 +96,12 @@ export type { TableActionButtonProps, TableActionButtonVariant } from './compone
|
|
|
96
96
|
export { TableIconText } from './components/TableIconText';
|
|
97
97
|
export type { TableIconTextProps } from './components/TableIconText';
|
|
98
98
|
|
|
99
|
+
export { Modal } from './components/Modal';
|
|
100
|
+
export type { ModalFooterAlign, ModalFooterLayout, ModalProps } from './components/Modal';
|
|
101
|
+
|
|
102
|
+
export { ConfirmModal } from './components/ConfirmModal';
|
|
103
|
+
export type { ConfirmModalProps } from './components/ConfirmModal';
|
|
104
|
+
|
|
99
105
|
export { useMediaQuery } from './hooks/useMediaQuery';
|
|
100
106
|
export { useOnClickOutside } from './hooks/useOnClickOutside';
|
|
101
107
|
export { cn } from './utils/cn';
|