@neoptocom/neopto-ui 0.9.0 → 0.9.2
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 -33
- package/dist/index.d.cts +24 -4
- package/dist/index.d.ts +24 -4
- package/dist/index.js +122 -34
- package/package.json +1 -1
- package/src/components/Card.tsx +93 -0
- package/src/components/Modal.tsx +63 -31
- package/src/index.ts +2 -0
- package/src/stories/Modal.stories.tsx +275 -0
package/dist/index.cjs
CHANGED
|
@@ -147,6 +147,86 @@ function BackgroundBlur({
|
|
|
147
147
|
}
|
|
148
148
|
);
|
|
149
149
|
}
|
|
150
|
+
function Card({
|
|
151
|
+
children,
|
|
152
|
+
className = "",
|
|
153
|
+
style,
|
|
154
|
+
showDecorations = true,
|
|
155
|
+
...props
|
|
156
|
+
}) {
|
|
157
|
+
const mergedStyle = {
|
|
158
|
+
borderRadius: "30px",
|
|
159
|
+
backgroundColor: "color-mix(in srgb, var(--surface) 85%, transparent)",
|
|
160
|
+
backdropFilter: "blur(75px)",
|
|
161
|
+
WebkitBackdropFilter: "blur(75px)",
|
|
162
|
+
// Safari support
|
|
163
|
+
color: "var(--fg)",
|
|
164
|
+
position: "relative",
|
|
165
|
+
overflow: "hidden",
|
|
166
|
+
transition: "background-color 0.3s ease, color 0.3s ease",
|
|
167
|
+
...style
|
|
168
|
+
};
|
|
169
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
170
|
+
"div",
|
|
171
|
+
{
|
|
172
|
+
className: `p-6 ${className}`,
|
|
173
|
+
style: mergedStyle,
|
|
174
|
+
...props,
|
|
175
|
+
children: [
|
|
176
|
+
showDecorations && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
177
|
+
"svg",
|
|
178
|
+
{
|
|
179
|
+
style: {
|
|
180
|
+
position: "absolute",
|
|
181
|
+
top: 0,
|
|
182
|
+
left: 0,
|
|
183
|
+
width: "100%",
|
|
184
|
+
height: "100%",
|
|
185
|
+
pointerEvents: "none",
|
|
186
|
+
zIndex: 0
|
|
187
|
+
},
|
|
188
|
+
viewBox: "0 0 967 745",
|
|
189
|
+
fill: "none",
|
|
190
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
191
|
+
preserveAspectRatio: "none",
|
|
192
|
+
children: [
|
|
193
|
+
/* @__PURE__ */ jsxRuntime.jsxs("defs", { children: [
|
|
194
|
+
/* @__PURE__ */ jsxRuntime.jsxs("linearGradient", { id: "paint0_linear_card", x1: "109", y1: "744.5", x2: "855", y2: "744.5", gradientUnits: "userSpaceOnUse", children: [
|
|
195
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { stopColor: "#4BDD74", stopOpacity: "0" }),
|
|
196
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0.5", stopColor: "#4BDD74" }),
|
|
197
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "1", stopColor: "#4BDD74", stopOpacity: "0" })
|
|
198
|
+
] }),
|
|
199
|
+
/* @__PURE__ */ jsxRuntime.jsxs("linearGradient", { id: "paint1_linear_card", x1: "967.5", y1: "10", x2: "967.5", y2: "652", gradientUnits: "userSpaceOnUse", children: [
|
|
200
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { stopColor: "#4BDD74", stopOpacity: "0" }),
|
|
201
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0.5", stopColor: "#4BDD74" }),
|
|
202
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "1", stopColor: "#4BDD74", stopOpacity: "0" })
|
|
203
|
+
] }),
|
|
204
|
+
/* @__PURE__ */ jsxRuntime.jsxs("linearGradient", { id: "paint2_linear_card", x1: "877", y1: "0.5", x2: "90", y2: "0.5", gradientUnits: "userSpaceOnUse", children: [
|
|
205
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { stopColor: "#4BDD74", stopOpacity: "0" }),
|
|
206
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0.5", stopColor: "#4BDD74" }),
|
|
207
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "1", stopColor: "#4BDD74", stopOpacity: "0" })
|
|
208
|
+
] }),
|
|
209
|
+
/* @__PURE__ */ jsxRuntime.jsxs("linearGradient", { id: "paint3_linear_card", x1: "0.5", y1: "34.5136", x2: "0.5", y2: "731.595", gradientUnits: "userSpaceOnUse", children: [
|
|
210
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { stopColor: "#4BDD74", stopOpacity: "0" }),
|
|
211
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0.5", stopColor: "#4BDD74" }),
|
|
212
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "1", stopColor: "#4BDD74", stopOpacity: "0" })
|
|
213
|
+
] })
|
|
214
|
+
] }),
|
|
215
|
+
/* @__PURE__ */ jsxRuntime.jsxs("g", { clipPath: "url(#clip0_card)", children: [
|
|
216
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { opacity: "0.8", x1: "855", y1: "744.5", x2: "109", y2: "744.5", stroke: "url(#paint0_linear_card)" }),
|
|
217
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "965.5", y1: "652", x2: "965.5", y2: "10", stroke: "url(#paint1_linear_card)" })
|
|
218
|
+
] }),
|
|
219
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { opacity: "0.6", x1: "90", y1: "0.5", x2: "877", y2: "0.5", stroke: "url(#paint2_linear_card)" }),
|
|
220
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "0.5", y1: "731.595", x2: "0.500027", y2: "34.5136", stroke: "url(#paint3_linear_card)" }),
|
|
221
|
+
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsx("clipPath", { id: "clip0_card", children: /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "966", height: "744", rx: "10", transform: "matrix(-1 0 0 1 967 1)", fill: "white" }) }) })
|
|
222
|
+
]
|
|
223
|
+
}
|
|
224
|
+
),
|
|
225
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "relative", zIndex: 1 }, children })
|
|
226
|
+
]
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
}
|
|
150
230
|
var Input = React2__namespace.forwardRef(
|
|
151
231
|
({ className, disabled, ...props }, ref) => {
|
|
152
232
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -179,10 +259,28 @@ function Modal({
|
|
|
179
259
|
open,
|
|
180
260
|
onClose,
|
|
181
261
|
title,
|
|
182
|
-
|
|
183
|
-
children
|
|
262
|
+
closeOnBackdrop = true,
|
|
263
|
+
children,
|
|
264
|
+
className = "",
|
|
265
|
+
zIndex = 50,
|
|
266
|
+
showDecorations = true
|
|
184
267
|
}) {
|
|
185
268
|
const [mounted, setMounted] = React2__namespace.useState(false);
|
|
269
|
+
const [isDark, setIsDark] = React2__namespace.useState(false);
|
|
270
|
+
React2__namespace.useEffect(() => {
|
|
271
|
+
const checkDarkMode = () => {
|
|
272
|
+
const hasDarkClass = document.documentElement.classList.contains("dark") || document.body.classList.contains("dark") || document.querySelector(".dark") !== null;
|
|
273
|
+
setIsDark(hasDarkClass);
|
|
274
|
+
};
|
|
275
|
+
checkDarkMode();
|
|
276
|
+
const observer = new MutationObserver(checkDarkMode);
|
|
277
|
+
observer.observe(document.documentElement, {
|
|
278
|
+
attributes: true,
|
|
279
|
+
attributeFilter: ["class"],
|
|
280
|
+
subtree: true
|
|
281
|
+
});
|
|
282
|
+
return () => observer.disconnect();
|
|
283
|
+
}, []);
|
|
186
284
|
useIsomorphicLayoutEffect(() => {
|
|
187
285
|
setMounted(true);
|
|
188
286
|
if (!open) return;
|
|
@@ -201,40 +299,30 @@ function Modal({
|
|
|
201
299
|
return () => window.removeEventListener("keydown", onKey);
|
|
202
300
|
}, [open, onClose]);
|
|
203
301
|
if (!mounted) return null;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
"div",
|
|
302
|
+
const modal = /* @__PURE__ */ jsxRuntime.jsx("div", { className: isDark ? "dark" : "", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
303
|
+
BackgroundBlur,
|
|
207
304
|
{
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
{
|
|
226
|
-
onClick: onClose,
|
|
227
|
-
className: "btn btn-outline",
|
|
228
|
-
type: "button",
|
|
229
|
-
children: "Close"
|
|
230
|
-
}
|
|
231
|
-
) })
|
|
232
|
-
] })
|
|
233
|
-
]
|
|
305
|
+
open,
|
|
306
|
+
onClose: closeOnBackdrop ? onClose : void 0,
|
|
307
|
+
zIndex,
|
|
308
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
309
|
+
Card,
|
|
310
|
+
{
|
|
311
|
+
className: `w-full max-w-lg ${className}`,
|
|
312
|
+
role: "dialog",
|
|
313
|
+
"aria-modal": "true",
|
|
314
|
+
"aria-labelledby": title ? "modal-title" : void 0,
|
|
315
|
+
showDecorations,
|
|
316
|
+
children: [
|
|
317
|
+
title && /* @__PURE__ */ jsxRuntime.jsx("h2", { id: "modal-title", className: "mb-4 text-xl font-semibold", children: title }),
|
|
318
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { children })
|
|
319
|
+
]
|
|
320
|
+
}
|
|
321
|
+
)
|
|
234
322
|
}
|
|
235
|
-
);
|
|
323
|
+
) });
|
|
236
324
|
const container = document.body;
|
|
237
|
-
return reactDom.createPortal(
|
|
325
|
+
return reactDom.createPortal(modal, container);
|
|
238
326
|
}
|
|
239
327
|
var typoStyles = {
|
|
240
328
|
"display-lg": { fontSize: "57px", lineHeight: "64px", letterSpacing: "-0.25px" },
|
|
@@ -1211,6 +1299,7 @@ exports.Avatar = Avatar;
|
|
|
1211
1299
|
exports.AvatarGroup = AvatarGroup;
|
|
1212
1300
|
exports.BackgroundBlur = BackgroundBlur;
|
|
1213
1301
|
exports.Button = Button;
|
|
1302
|
+
exports.Card = Card;
|
|
1214
1303
|
exports.ChatButton = ChatButton_default;
|
|
1215
1304
|
exports.Chip = Chip;
|
|
1216
1305
|
exports.Counter = Counter;
|
package/dist/index.d.cts
CHANGED
|
@@ -40,18 +40,38 @@ type BackgroundBlurProps = {
|
|
|
40
40
|
};
|
|
41
41
|
declare function BackgroundBlur({ open, children, onClose, zIndex, className, }: BackgroundBlurProps): react_jsx_runtime.JSX.Element | null;
|
|
42
42
|
|
|
43
|
+
type CardProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
44
|
+
/** Content to render inside the card */
|
|
45
|
+
children: React.ReactNode;
|
|
46
|
+
/** Additional CSS classes */
|
|
47
|
+
className?: string;
|
|
48
|
+
/** Show decorative elements (default: true) */
|
|
49
|
+
showDecorations?: boolean;
|
|
50
|
+
};
|
|
51
|
+
declare function Card({ children, className, style, showDecorations, ...props }: CardProps): react_jsx_runtime.JSX.Element;
|
|
52
|
+
|
|
43
53
|
type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>;
|
|
44
54
|
declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
|
|
45
55
|
|
|
46
56
|
type ModalProps = {
|
|
57
|
+
/** Whether the modal is open */
|
|
47
58
|
open: boolean;
|
|
59
|
+
/** Callback when modal should close */
|
|
48
60
|
onClose?: () => void;
|
|
61
|
+
/** Modal content */
|
|
49
62
|
children?: React.ReactNode;
|
|
63
|
+
/** Optional title (rendered as heading) */
|
|
50
64
|
title?: string;
|
|
51
|
-
/** When true, closes when the
|
|
52
|
-
|
|
65
|
+
/** When true, closes when the backdrop is clicked (default: true) */
|
|
66
|
+
closeOnBackdrop?: boolean;
|
|
67
|
+
/** Custom className for the Card */
|
|
68
|
+
className?: string;
|
|
69
|
+
/** z-index for the modal (default: 50) */
|
|
70
|
+
zIndex?: number;
|
|
71
|
+
/** Show decorative elements on the Card (default: true) */
|
|
72
|
+
showDecorations?: boolean;
|
|
53
73
|
};
|
|
54
|
-
declare function Modal({ open, onClose, title,
|
|
74
|
+
declare function Modal({ open, onClose, title, closeOnBackdrop, children, className, zIndex, showDecorations, }: ModalProps): React.ReactPortal | null;
|
|
55
75
|
|
|
56
76
|
type TypoVariant = "display-lg" | "display-md" | "display-sm" | "headline-lg" | "headline-md" | "headline-sm" | "title-lg" | "title-md" | "title-sm" | "label-lg" | "label-md" | "label-sm" | "body-lg" | "body-md" | "body-sm" | "button";
|
|
57
77
|
type TypoWeight = "normal" | "medium" | "semibold" | "bold";
|
|
@@ -273,4 +293,4 @@ interface AnimatedBgRectangleProps {
|
|
|
273
293
|
}
|
|
274
294
|
declare const AnimatedBgRectangle: ({ colors, delay }: AnimatedBgRectangleProps) => react_jsx_runtime.JSX.Element;
|
|
275
295
|
|
|
276
|
-
export { AnimatedBgCircle, AnimatedBgRectangle, AppBackground, type AppBackgroundProps, Autocomplete, type AutocompleteOption, type AutocompleteProps, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, BackgroundBlur, type BackgroundBlurProps, Button, type ButtonProps, ChatButton, type ChatButtonProps, Chip, type ChipProps, Counter, type CounterProps, Icon, IconButton, type IconButtonProps, type IconProps, Input, type InputProps, Modal, type ModalProps, Search, type SearchOption, type SearchProps, Skeleton, type SkeletonProps, Typo, type TypoProps, type TypoVariant, type TypoWeight, index as assets };
|
|
296
|
+
export { AnimatedBgCircle, AnimatedBgRectangle, AppBackground, type AppBackgroundProps, Autocomplete, type AutocompleteOption, type AutocompleteProps, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, BackgroundBlur, type BackgroundBlurProps, Button, type ButtonProps, Card, type CardProps, ChatButton, type ChatButtonProps, Chip, type ChipProps, Counter, type CounterProps, Icon, IconButton, type IconButtonProps, type IconProps, Input, type InputProps, Modal, type ModalProps, Search, type SearchOption, type SearchProps, Skeleton, type SkeletonProps, Typo, type TypoProps, type TypoVariant, type TypoWeight, index as assets };
|
package/dist/index.d.ts
CHANGED
|
@@ -40,18 +40,38 @@ type BackgroundBlurProps = {
|
|
|
40
40
|
};
|
|
41
41
|
declare function BackgroundBlur({ open, children, onClose, zIndex, className, }: BackgroundBlurProps): react_jsx_runtime.JSX.Element | null;
|
|
42
42
|
|
|
43
|
+
type CardProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
44
|
+
/** Content to render inside the card */
|
|
45
|
+
children: React.ReactNode;
|
|
46
|
+
/** Additional CSS classes */
|
|
47
|
+
className?: string;
|
|
48
|
+
/** Show decorative elements (default: true) */
|
|
49
|
+
showDecorations?: boolean;
|
|
50
|
+
};
|
|
51
|
+
declare function Card({ children, className, style, showDecorations, ...props }: CardProps): react_jsx_runtime.JSX.Element;
|
|
52
|
+
|
|
43
53
|
type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>;
|
|
44
54
|
declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
|
|
45
55
|
|
|
46
56
|
type ModalProps = {
|
|
57
|
+
/** Whether the modal is open */
|
|
47
58
|
open: boolean;
|
|
59
|
+
/** Callback when modal should close */
|
|
48
60
|
onClose?: () => void;
|
|
61
|
+
/** Modal content */
|
|
49
62
|
children?: React.ReactNode;
|
|
63
|
+
/** Optional title (rendered as heading) */
|
|
50
64
|
title?: string;
|
|
51
|
-
/** When true, closes when the
|
|
52
|
-
|
|
65
|
+
/** When true, closes when the backdrop is clicked (default: true) */
|
|
66
|
+
closeOnBackdrop?: boolean;
|
|
67
|
+
/** Custom className for the Card */
|
|
68
|
+
className?: string;
|
|
69
|
+
/** z-index for the modal (default: 50) */
|
|
70
|
+
zIndex?: number;
|
|
71
|
+
/** Show decorative elements on the Card (default: true) */
|
|
72
|
+
showDecorations?: boolean;
|
|
53
73
|
};
|
|
54
|
-
declare function Modal({ open, onClose, title,
|
|
74
|
+
declare function Modal({ open, onClose, title, closeOnBackdrop, children, className, zIndex, showDecorations, }: ModalProps): React.ReactPortal | null;
|
|
55
75
|
|
|
56
76
|
type TypoVariant = "display-lg" | "display-md" | "display-sm" | "headline-lg" | "headline-md" | "headline-sm" | "title-lg" | "title-md" | "title-sm" | "label-lg" | "label-md" | "label-sm" | "body-lg" | "body-md" | "body-sm" | "button";
|
|
57
77
|
type TypoWeight = "normal" | "medium" | "semibold" | "bold";
|
|
@@ -273,4 +293,4 @@ interface AnimatedBgRectangleProps {
|
|
|
273
293
|
}
|
|
274
294
|
declare const AnimatedBgRectangle: ({ colors, delay }: AnimatedBgRectangleProps) => react_jsx_runtime.JSX.Element;
|
|
275
295
|
|
|
276
|
-
export { AnimatedBgCircle, AnimatedBgRectangle, AppBackground, type AppBackgroundProps, Autocomplete, type AutocompleteOption, type AutocompleteProps, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, BackgroundBlur, type BackgroundBlurProps, Button, type ButtonProps, ChatButton, type ChatButtonProps, Chip, type ChipProps, Counter, type CounterProps, Icon, IconButton, type IconButtonProps, type IconProps, Input, type InputProps, Modal, type ModalProps, Search, type SearchOption, type SearchProps, Skeleton, type SkeletonProps, Typo, type TypoProps, type TypoVariant, type TypoWeight, index as assets };
|
|
296
|
+
export { AnimatedBgCircle, AnimatedBgRectangle, AppBackground, type AppBackgroundProps, Autocomplete, type AutocompleteOption, type AutocompleteProps, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, BackgroundBlur, type BackgroundBlurProps, Button, type ButtonProps, Card, type CardProps, ChatButton, type ChatButtonProps, Chip, type ChipProps, Counter, type CounterProps, Icon, IconButton, type IconButtonProps, type IconProps, Input, type InputProps, Modal, type ModalProps, Search, type SearchOption, type SearchProps, Skeleton, type SkeletonProps, Typo, type TypoProps, type TypoVariant, type TypoWeight, index as assets };
|
package/dist/index.js
CHANGED
|
@@ -126,6 +126,86 @@ function BackgroundBlur({
|
|
|
126
126
|
}
|
|
127
127
|
);
|
|
128
128
|
}
|
|
129
|
+
function Card({
|
|
130
|
+
children,
|
|
131
|
+
className = "",
|
|
132
|
+
style,
|
|
133
|
+
showDecorations = true,
|
|
134
|
+
...props
|
|
135
|
+
}) {
|
|
136
|
+
const mergedStyle = {
|
|
137
|
+
borderRadius: "30px",
|
|
138
|
+
backgroundColor: "color-mix(in srgb, var(--surface) 85%, transparent)",
|
|
139
|
+
backdropFilter: "blur(75px)",
|
|
140
|
+
WebkitBackdropFilter: "blur(75px)",
|
|
141
|
+
// Safari support
|
|
142
|
+
color: "var(--fg)",
|
|
143
|
+
position: "relative",
|
|
144
|
+
overflow: "hidden",
|
|
145
|
+
transition: "background-color 0.3s ease, color 0.3s ease",
|
|
146
|
+
...style
|
|
147
|
+
};
|
|
148
|
+
return /* @__PURE__ */ jsxs(
|
|
149
|
+
"div",
|
|
150
|
+
{
|
|
151
|
+
className: `p-6 ${className}`,
|
|
152
|
+
style: mergedStyle,
|
|
153
|
+
...props,
|
|
154
|
+
children: [
|
|
155
|
+
showDecorations && /* @__PURE__ */ jsxs(
|
|
156
|
+
"svg",
|
|
157
|
+
{
|
|
158
|
+
style: {
|
|
159
|
+
position: "absolute",
|
|
160
|
+
top: 0,
|
|
161
|
+
left: 0,
|
|
162
|
+
width: "100%",
|
|
163
|
+
height: "100%",
|
|
164
|
+
pointerEvents: "none",
|
|
165
|
+
zIndex: 0
|
|
166
|
+
},
|
|
167
|
+
viewBox: "0 0 967 745",
|
|
168
|
+
fill: "none",
|
|
169
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
170
|
+
preserveAspectRatio: "none",
|
|
171
|
+
children: [
|
|
172
|
+
/* @__PURE__ */ jsxs("defs", { children: [
|
|
173
|
+
/* @__PURE__ */ jsxs("linearGradient", { id: "paint0_linear_card", x1: "109", y1: "744.5", x2: "855", y2: "744.5", gradientUnits: "userSpaceOnUse", children: [
|
|
174
|
+
/* @__PURE__ */ jsx("stop", { stopColor: "#4BDD74", stopOpacity: "0" }),
|
|
175
|
+
/* @__PURE__ */ jsx("stop", { offset: "0.5", stopColor: "#4BDD74" }),
|
|
176
|
+
/* @__PURE__ */ jsx("stop", { offset: "1", stopColor: "#4BDD74", stopOpacity: "0" })
|
|
177
|
+
] }),
|
|
178
|
+
/* @__PURE__ */ jsxs("linearGradient", { id: "paint1_linear_card", x1: "967.5", y1: "10", x2: "967.5", y2: "652", gradientUnits: "userSpaceOnUse", children: [
|
|
179
|
+
/* @__PURE__ */ jsx("stop", { stopColor: "#4BDD74", stopOpacity: "0" }),
|
|
180
|
+
/* @__PURE__ */ jsx("stop", { offset: "0.5", stopColor: "#4BDD74" }),
|
|
181
|
+
/* @__PURE__ */ jsx("stop", { offset: "1", stopColor: "#4BDD74", stopOpacity: "0" })
|
|
182
|
+
] }),
|
|
183
|
+
/* @__PURE__ */ jsxs("linearGradient", { id: "paint2_linear_card", x1: "877", y1: "0.5", x2: "90", y2: "0.5", gradientUnits: "userSpaceOnUse", children: [
|
|
184
|
+
/* @__PURE__ */ jsx("stop", { stopColor: "#4BDD74", stopOpacity: "0" }),
|
|
185
|
+
/* @__PURE__ */ jsx("stop", { offset: "0.5", stopColor: "#4BDD74" }),
|
|
186
|
+
/* @__PURE__ */ jsx("stop", { offset: "1", stopColor: "#4BDD74", stopOpacity: "0" })
|
|
187
|
+
] }),
|
|
188
|
+
/* @__PURE__ */ jsxs("linearGradient", { id: "paint3_linear_card", x1: "0.5", y1: "34.5136", x2: "0.5", y2: "731.595", gradientUnits: "userSpaceOnUse", children: [
|
|
189
|
+
/* @__PURE__ */ jsx("stop", { stopColor: "#4BDD74", stopOpacity: "0" }),
|
|
190
|
+
/* @__PURE__ */ jsx("stop", { offset: "0.5", stopColor: "#4BDD74" }),
|
|
191
|
+
/* @__PURE__ */ jsx("stop", { offset: "1", stopColor: "#4BDD74", stopOpacity: "0" })
|
|
192
|
+
] })
|
|
193
|
+
] }),
|
|
194
|
+
/* @__PURE__ */ jsxs("g", { clipPath: "url(#clip0_card)", children: [
|
|
195
|
+
/* @__PURE__ */ jsx("line", { opacity: "0.8", x1: "855", y1: "744.5", x2: "109", y2: "744.5", stroke: "url(#paint0_linear_card)" }),
|
|
196
|
+
/* @__PURE__ */ jsx("line", { x1: "965.5", y1: "652", x2: "965.5", y2: "10", stroke: "url(#paint1_linear_card)" })
|
|
197
|
+
] }),
|
|
198
|
+
/* @__PURE__ */ jsx("line", { opacity: "0.6", x1: "90", y1: "0.5", x2: "877", y2: "0.5", stroke: "url(#paint2_linear_card)" }),
|
|
199
|
+
/* @__PURE__ */ jsx("line", { x1: "0.5", y1: "731.595", x2: "0.500027", y2: "34.5136", stroke: "url(#paint3_linear_card)" }),
|
|
200
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: "clip0_card", children: /* @__PURE__ */ jsx("rect", { width: "966", height: "744", rx: "10", transform: "matrix(-1 0 0 1 967 1)", fill: "white" }) }) })
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
),
|
|
204
|
+
/* @__PURE__ */ jsx("div", { style: { position: "relative", zIndex: 1 }, children })
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
}
|
|
129
209
|
var Input = React2.forwardRef(
|
|
130
210
|
({ className, disabled, ...props }, ref) => {
|
|
131
211
|
return /* @__PURE__ */ jsx(
|
|
@@ -158,10 +238,28 @@ function Modal({
|
|
|
158
238
|
open,
|
|
159
239
|
onClose,
|
|
160
240
|
title,
|
|
161
|
-
|
|
162
|
-
children
|
|
241
|
+
closeOnBackdrop = true,
|
|
242
|
+
children,
|
|
243
|
+
className = "",
|
|
244
|
+
zIndex = 50,
|
|
245
|
+
showDecorations = true
|
|
163
246
|
}) {
|
|
164
247
|
const [mounted, setMounted] = React2.useState(false);
|
|
248
|
+
const [isDark, setIsDark] = React2.useState(false);
|
|
249
|
+
React2.useEffect(() => {
|
|
250
|
+
const checkDarkMode = () => {
|
|
251
|
+
const hasDarkClass = document.documentElement.classList.contains("dark") || document.body.classList.contains("dark") || document.querySelector(".dark") !== null;
|
|
252
|
+
setIsDark(hasDarkClass);
|
|
253
|
+
};
|
|
254
|
+
checkDarkMode();
|
|
255
|
+
const observer = new MutationObserver(checkDarkMode);
|
|
256
|
+
observer.observe(document.documentElement, {
|
|
257
|
+
attributes: true,
|
|
258
|
+
attributeFilter: ["class"],
|
|
259
|
+
subtree: true
|
|
260
|
+
});
|
|
261
|
+
return () => observer.disconnect();
|
|
262
|
+
}, []);
|
|
165
263
|
useIsomorphicLayoutEffect(() => {
|
|
166
264
|
setMounted(true);
|
|
167
265
|
if (!open) return;
|
|
@@ -180,40 +278,30 @@ function Modal({
|
|
|
180
278
|
return () => window.removeEventListener("keydown", onKey);
|
|
181
279
|
}, [open, onClose]);
|
|
182
280
|
if (!mounted) return null;
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
"div",
|
|
281
|
+
const modal = /* @__PURE__ */ jsx("div", { className: isDark ? "dark" : "", children: /* @__PURE__ */ jsx(
|
|
282
|
+
BackgroundBlur,
|
|
186
283
|
{
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
{
|
|
205
|
-
onClick: onClose,
|
|
206
|
-
className: "btn btn-outline",
|
|
207
|
-
type: "button",
|
|
208
|
-
children: "Close"
|
|
209
|
-
}
|
|
210
|
-
) })
|
|
211
|
-
] })
|
|
212
|
-
]
|
|
284
|
+
open,
|
|
285
|
+
onClose: closeOnBackdrop ? onClose : void 0,
|
|
286
|
+
zIndex,
|
|
287
|
+
children: /* @__PURE__ */ jsxs(
|
|
288
|
+
Card,
|
|
289
|
+
{
|
|
290
|
+
className: `w-full max-w-lg ${className}`,
|
|
291
|
+
role: "dialog",
|
|
292
|
+
"aria-modal": "true",
|
|
293
|
+
"aria-labelledby": title ? "modal-title" : void 0,
|
|
294
|
+
showDecorations,
|
|
295
|
+
children: [
|
|
296
|
+
title && /* @__PURE__ */ jsx("h2", { id: "modal-title", className: "mb-4 text-xl font-semibold", children: title }),
|
|
297
|
+
/* @__PURE__ */ jsx("div", { children })
|
|
298
|
+
]
|
|
299
|
+
}
|
|
300
|
+
)
|
|
213
301
|
}
|
|
214
|
-
);
|
|
302
|
+
) });
|
|
215
303
|
const container = document.body;
|
|
216
|
-
return createPortal(
|
|
304
|
+
return createPortal(modal, container);
|
|
217
305
|
}
|
|
218
306
|
var typoStyles = {
|
|
219
307
|
"display-lg": { fontSize: "57px", lineHeight: "64px", letterSpacing: "-0.25px" },
|
|
@@ -1182,4 +1270,4 @@ var ChatButton = ({
|
|
|
1182
1270
|
};
|
|
1183
1271
|
var ChatButton_default = ChatButton;
|
|
1184
1272
|
|
|
1185
|
-
export { AnimatedBgCircle_default as AnimatedBgCircle, AnimatedBgRectangle_default as AnimatedBgRectangle, AppBackground, Autocomplete, Avatar, AvatarGroup, BackgroundBlur, Button, ChatButton_default as ChatButton, Chip, Counter, Icon, IconButton, Input, Modal, Search, Skeleton, Typo, assets_exports as assets };
|
|
1273
|
+
export { AnimatedBgCircle_default as AnimatedBgCircle, AnimatedBgRectangle_default as AnimatedBgRectangle, AppBackground, Autocomplete, Avatar, AvatarGroup, BackgroundBlur, Button, Card, ChatButton_default as ChatButton, Chip, Counter, Icon, IconButton, Input, Modal, Search, Skeleton, Typo, assets_exports as assets };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neoptocom/neopto-ui",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A modern React component library built with Tailwind CSS v4 and TypeScript. Features dark mode, design tokens, and comprehensive Storybook documentation. Requires Tailwind v4+.",
|
|
6
6
|
"keywords": [
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
export type CardProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
4
|
+
/** Content to render inside the card */
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
/** Additional CSS classes */
|
|
7
|
+
className?: string;
|
|
8
|
+
/** Show decorative elements (default: true) */
|
|
9
|
+
showDecorations?: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function Card({
|
|
13
|
+
children,
|
|
14
|
+
className = "",
|
|
15
|
+
style,
|
|
16
|
+
showDecorations = true,
|
|
17
|
+
...props
|
|
18
|
+
}: CardProps) {
|
|
19
|
+
// Merge user styles with card styles
|
|
20
|
+
const mergedStyle: React.CSSProperties = {
|
|
21
|
+
borderRadius: "30px",
|
|
22
|
+
backgroundColor: "color-mix(in srgb, var(--surface) 85%, transparent)",
|
|
23
|
+
backdropFilter: "blur(75px)",
|
|
24
|
+
WebkitBackdropFilter: "blur(75px)", // Safari support
|
|
25
|
+
color: "var(--fg)",
|
|
26
|
+
position: "relative",
|
|
27
|
+
overflow: "hidden",
|
|
28
|
+
transition: "background-color 0.3s ease, color 0.3s ease",
|
|
29
|
+
...style,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
className={`p-6 ${className}`}
|
|
35
|
+
style={mergedStyle}
|
|
36
|
+
{...props}
|
|
37
|
+
>
|
|
38
|
+
{showDecorations && (
|
|
39
|
+
<svg
|
|
40
|
+
style={{
|
|
41
|
+
position: "absolute",
|
|
42
|
+
top: 0,
|
|
43
|
+
left: 0,
|
|
44
|
+
width: "100%",
|
|
45
|
+
height: "100%",
|
|
46
|
+
pointerEvents: "none",
|
|
47
|
+
zIndex: 0,
|
|
48
|
+
}}
|
|
49
|
+
viewBox="0 0 967 745"
|
|
50
|
+
fill="none"
|
|
51
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
52
|
+
preserveAspectRatio="none"
|
|
53
|
+
>
|
|
54
|
+
<defs>
|
|
55
|
+
<linearGradient id="paint0_linear_card" x1="109" y1="744.5" x2="855" y2="744.5" gradientUnits="userSpaceOnUse">
|
|
56
|
+
<stop stopColor="#4BDD74" stopOpacity="0"/>
|
|
57
|
+
<stop offset="0.5" stopColor="#4BDD74"/>
|
|
58
|
+
<stop offset="1" stopColor="#4BDD74" stopOpacity="0"/>
|
|
59
|
+
</linearGradient>
|
|
60
|
+
<linearGradient id="paint1_linear_card" x1="967.5" y1="10" x2="967.5" y2="652" gradientUnits="userSpaceOnUse">
|
|
61
|
+
<stop stopColor="#4BDD74" stopOpacity="0"/>
|
|
62
|
+
<stop offset="0.5" stopColor="#4BDD74"/>
|
|
63
|
+
<stop offset="1" stopColor="#4BDD74" stopOpacity="0"/>
|
|
64
|
+
</linearGradient>
|
|
65
|
+
<linearGradient id="paint2_linear_card" x1="877" y1="0.5" x2="90" y2="0.5" gradientUnits="userSpaceOnUse">
|
|
66
|
+
<stop stopColor="#4BDD74" stopOpacity="0"/>
|
|
67
|
+
<stop offset="0.5" stopColor="#4BDD74"/>
|
|
68
|
+
<stop offset="1" stopColor="#4BDD74" stopOpacity="0"/>
|
|
69
|
+
</linearGradient>
|
|
70
|
+
<linearGradient id="paint3_linear_card" x1="0.5" y1="34.5136" x2="0.5" y2="731.595" gradientUnits="userSpaceOnUse">
|
|
71
|
+
<stop stopColor="#4BDD74" stopOpacity="0"/>
|
|
72
|
+
<stop offset="0.5" stopColor="#4BDD74"/>
|
|
73
|
+
<stop offset="1" stopColor="#4BDD74" stopOpacity="0"/>
|
|
74
|
+
</linearGradient>
|
|
75
|
+
</defs>
|
|
76
|
+
<g clipPath="url(#clip0_card)"><line opacity="0.8" x1="855" y1="744.5" x2="109" y2="744.5" stroke="url(#paint0_linear_card)"/>
|
|
77
|
+
<line x1="965.5" y1="652" x2="965.5" y2="10" stroke="url(#paint1_linear_card)"/>
|
|
78
|
+
</g>
|
|
79
|
+
<line opacity="0.6" x1="90" y1="0.5" x2="877" y2="0.5" stroke="url(#paint2_linear_card)"/>
|
|
80
|
+
<line x1="0.5" y1="731.595" x2="0.500027" y2="34.5136" stroke="url(#paint3_linear_card)"/>
|
|
81
|
+
<defs>
|
|
82
|
+
<clipPath id="clip0_card">
|
|
83
|
+
<rect width="966" height="744" rx="10" transform="matrix(-1 0 0 1 967 1)" fill="white"/>
|
|
84
|
+
</clipPath>
|
|
85
|
+
</defs>
|
|
86
|
+
</svg>
|
|
87
|
+
)}
|
|
88
|
+
<div style={{ position: "relative", zIndex: 1 }}>
|
|
89
|
+
{children}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
package/src/components/Modal.tsx
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { createPortal } from "react-dom";
|
|
3
|
+
import { BackgroundBlur } from "./BackgroundBlur";
|
|
4
|
+
import { Card } from "./Card";
|
|
3
5
|
|
|
4
6
|
export type ModalProps = {
|
|
7
|
+
/** Whether the modal is open */
|
|
5
8
|
open: boolean;
|
|
9
|
+
/** Callback when modal should close */
|
|
6
10
|
onClose?: () => void;
|
|
11
|
+
/** Modal content */
|
|
7
12
|
children?: React.ReactNode;
|
|
13
|
+
/** Optional title (rendered as heading) */
|
|
8
14
|
title?: string;
|
|
9
|
-
/** When true, closes when the
|
|
10
|
-
|
|
15
|
+
/** When true, closes when the backdrop is clicked (default: true) */
|
|
16
|
+
closeOnBackdrop?: boolean;
|
|
17
|
+
/** Custom className for the Card */
|
|
18
|
+
className?: string;
|
|
19
|
+
/** z-index for the modal (default: 50) */
|
|
20
|
+
zIndex?: number;
|
|
21
|
+
/** Show decorative elements on the Card (default: true) */
|
|
22
|
+
showDecorations?: boolean;
|
|
11
23
|
};
|
|
12
24
|
|
|
13
25
|
function useIsomorphicLayoutEffect(effect: React.EffectCallback, deps?: React.DependencyList) {
|
|
@@ -19,10 +31,36 @@ export function Modal({
|
|
|
19
31
|
open,
|
|
20
32
|
onClose,
|
|
21
33
|
title,
|
|
22
|
-
|
|
23
|
-
children
|
|
34
|
+
closeOnBackdrop = true,
|
|
35
|
+
children,
|
|
36
|
+
className = "",
|
|
37
|
+
zIndex = 50,
|
|
38
|
+
showDecorations = true,
|
|
24
39
|
}: ModalProps) {
|
|
25
40
|
const [mounted, setMounted] = React.useState(false);
|
|
41
|
+
const [isDark, setIsDark] = React.useState(false);
|
|
42
|
+
|
|
43
|
+
// Detect dark mode
|
|
44
|
+
React.useEffect(() => {
|
|
45
|
+
const checkDarkMode = () => {
|
|
46
|
+
const hasDarkClass = document.documentElement.classList.contains("dark") ||
|
|
47
|
+
document.body.classList.contains("dark") ||
|
|
48
|
+
document.querySelector(".dark") !== null;
|
|
49
|
+
setIsDark(hasDarkClass);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
checkDarkMode();
|
|
53
|
+
|
|
54
|
+
// Listen for changes to dark mode
|
|
55
|
+
const observer = new MutationObserver(checkDarkMode);
|
|
56
|
+
observer.observe(document.documentElement, {
|
|
57
|
+
attributes: true,
|
|
58
|
+
attributeFilter: ["class"],
|
|
59
|
+
subtree: true,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return () => observer.disconnect();
|
|
63
|
+
}, []);
|
|
26
64
|
|
|
27
65
|
// Prevent body scroll when open
|
|
28
66
|
useIsomorphicLayoutEffect(() => {
|
|
@@ -35,6 +73,7 @@ export function Modal({
|
|
|
35
73
|
};
|
|
36
74
|
}, [open]);
|
|
37
75
|
|
|
76
|
+
// ESC key to close
|
|
38
77
|
React.useEffect(() => {
|
|
39
78
|
if (!open) return;
|
|
40
79
|
const onKey = (e: KeyboardEvent) => {
|
|
@@ -45,39 +84,32 @@ export function Modal({
|
|
|
45
84
|
}, [open, onClose]);
|
|
46
85
|
|
|
47
86
|
if (!mounted) return null;
|
|
48
|
-
if (!open) return null;
|
|
49
87
|
|
|
50
|
-
const
|
|
51
|
-
<div
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
88
|
+
const modal = (
|
|
89
|
+
<div className={isDark ? "dark" : ""}>
|
|
90
|
+
<BackgroundBlur
|
|
91
|
+
open={open}
|
|
92
|
+
onClose={closeOnBackdrop ? onClose : undefined}
|
|
93
|
+
zIndex={zIndex}
|
|
94
|
+
>
|
|
95
|
+
<Card
|
|
96
|
+
className={`w-full max-w-lg ${className}`}
|
|
97
|
+
role="dialog"
|
|
98
|
+
aria-modal="true"
|
|
99
|
+
aria-labelledby={title ? "modal-title" : undefined}
|
|
100
|
+
showDecorations={showDecorations}
|
|
101
|
+
>
|
|
102
|
+
{title && (
|
|
103
|
+
<h2 id="modal-title" className="mb-4 text-xl font-semibold">
|
|
64
104
|
{title}
|
|
65
105
|
</h2>
|
|
66
|
-
)
|
|
106
|
+
)}
|
|
67
107
|
<div>{children}</div>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
onClick={onClose}
|
|
71
|
-
className="btn btn-outline"
|
|
72
|
-
type="button"
|
|
73
|
-
>
|
|
74
|
-
Close
|
|
75
|
-
</button>
|
|
76
|
-
</div>
|
|
77
|
-
</div>
|
|
108
|
+
</Card>
|
|
109
|
+
</BackgroundBlur>
|
|
78
110
|
</div>
|
|
79
111
|
);
|
|
80
112
|
|
|
81
113
|
const container = document.body;
|
|
82
|
-
return createPortal(
|
|
114
|
+
return createPortal(modal, container);
|
|
83
115
|
}
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * as assets from "./assets";
|
|
|
4
4
|
// Components
|
|
5
5
|
export { AppBackground } from "./components/AppBackground";
|
|
6
6
|
export { BackgroundBlur } from "./components/BackgroundBlur";
|
|
7
|
+
export { Card } from "./components/Card";
|
|
7
8
|
export * from "./components/Input";
|
|
8
9
|
export * from "./components/Modal";
|
|
9
10
|
export { default as Typo } from "./components/Typo";
|
|
@@ -22,6 +23,7 @@ export * from "./components/Chat";
|
|
|
22
23
|
// Types
|
|
23
24
|
export type { AppBackgroundProps } from "./components/AppBackground";
|
|
24
25
|
export type { BackgroundBlurProps } from "./components/BackgroundBlur";
|
|
26
|
+
export type { CardProps } from "./components/Card";
|
|
25
27
|
export type { InputProps } from "./components/Input";
|
|
26
28
|
export type { ModalProps } from "./components/Modal";
|
|
27
29
|
export type { TypoProps, TypoVariant, TypoWeight } from "./components/Typo";
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Modal } from "../components/Modal";
|
|
4
|
+
import { Button } from "../components/Button";
|
|
5
|
+
import Typo from "../components/Typo";
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: "Components/Modal",
|
|
9
|
+
component: Modal,
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: "fullscreen",
|
|
12
|
+
},
|
|
13
|
+
tags: ["autodocs"],
|
|
14
|
+
} satisfies Meta<typeof Modal>;
|
|
15
|
+
|
|
16
|
+
export default meta;
|
|
17
|
+
type Story = StoryObj<typeof meta>;
|
|
18
|
+
|
|
19
|
+
// Helper component to manage modal state
|
|
20
|
+
function ModalDemo(props: Partial<React.ComponentProps<typeof Modal>>) {
|
|
21
|
+
const [open, setOpen] = useState(false);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="p-8">
|
|
25
|
+
<Button onClick={() => setOpen(true)}>Open Modal</Button>
|
|
26
|
+
<Modal open={open} onClose={() => setOpen(false)} {...props}>
|
|
27
|
+
{props.children}
|
|
28
|
+
</Modal>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const Default: Story = {
|
|
34
|
+
args: { open: false },
|
|
35
|
+
render: () => (
|
|
36
|
+
<ModalDemo>
|
|
37
|
+
<Typo variant="headline-md">Welcome!</Typo>
|
|
38
|
+
<Typo variant="body-md" className="mt-4">
|
|
39
|
+
This is a simple modal with custom content. Click outside or press ESC to close.
|
|
40
|
+
</Typo>
|
|
41
|
+
</ModalDemo>
|
|
42
|
+
),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const WithTitle: Story = {
|
|
46
|
+
args: { open: false },
|
|
47
|
+
render: () => (
|
|
48
|
+
<ModalDemo title="Modal Title">
|
|
49
|
+
<Typo variant="body-md">
|
|
50
|
+
This modal includes a title heading. You can still add any content you want below it.
|
|
51
|
+
</Typo>
|
|
52
|
+
<Typo variant="body-sm" className="mt-4 text-[var(--muted-fg)]">
|
|
53
|
+
Try clicking the backdrop to close, or press ESC.
|
|
54
|
+
</Typo>
|
|
55
|
+
</ModalDemo>
|
|
56
|
+
),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const NoBackdropClose: Story = {
|
|
60
|
+
args: { open: false },
|
|
61
|
+
render: () => (
|
|
62
|
+
<ModalDemo
|
|
63
|
+
title="Important Notice"
|
|
64
|
+
closeOnBackdrop={false}
|
|
65
|
+
>
|
|
66
|
+
<Typo variant="body-md">
|
|
67
|
+
This modal cannot be closed by clicking the backdrop.
|
|
68
|
+
</Typo>
|
|
69
|
+
<Typo variant="body-md" className="mt-4">
|
|
70
|
+
You must use the button below or press ESC to close.
|
|
71
|
+
</Typo>
|
|
72
|
+
<div className="mt-6">
|
|
73
|
+
<Button onClick={() => {}}>Acknowledge</Button>
|
|
74
|
+
</div>
|
|
75
|
+
</ModalDemo>
|
|
76
|
+
),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const CustomStyling: Story = {
|
|
80
|
+
args: { open: false },
|
|
81
|
+
render: () => (
|
|
82
|
+
<ModalDemo
|
|
83
|
+
title="Large Modal"
|
|
84
|
+
className="max-w-2xl p-12"
|
|
85
|
+
>
|
|
86
|
+
<Typo variant="body-md">
|
|
87
|
+
This modal has custom styling with a larger max-width and more padding.
|
|
88
|
+
</Typo>
|
|
89
|
+
<div className="mt-6 grid grid-cols-2 gap-4">
|
|
90
|
+
<div className="p-4 bg-[var(--muted)] rounded-2xl">
|
|
91
|
+
<Typo variant="label-lg" bold="semibold">Feature 1</Typo>
|
|
92
|
+
<Typo variant="body-sm" className="mt-2">Description here</Typo>
|
|
93
|
+
</div>
|
|
94
|
+
<div className="p-4 bg-[var(--muted)] rounded-2xl">
|
|
95
|
+
<Typo variant="label-lg" bold="semibold">Feature 2</Typo>
|
|
96
|
+
<Typo variant="body-sm" className="mt-2">Description here</Typo>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</ModalDemo>
|
|
100
|
+
),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const FormModal: Story = {
|
|
104
|
+
args: { open: false },
|
|
105
|
+
render: () => {
|
|
106
|
+
const [open, setOpen] = useState(false);
|
|
107
|
+
const [name, setName] = useState("");
|
|
108
|
+
const [email, setEmail] = useState("");
|
|
109
|
+
|
|
110
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
111
|
+
e.preventDefault();
|
|
112
|
+
alert(`Submitted: ${name}, ${email}`);
|
|
113
|
+
setOpen(false);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<div className="p-8">
|
|
118
|
+
<Button onClick={() => setOpen(true)}>Open Form Modal</Button>
|
|
119
|
+
<Modal
|
|
120
|
+
open={open}
|
|
121
|
+
onClose={() => setOpen(false)}
|
|
122
|
+
title="Contact Form"
|
|
123
|
+
>
|
|
124
|
+
<form onSubmit={handleSubmit}>
|
|
125
|
+
<div className="space-y-4">
|
|
126
|
+
<div>
|
|
127
|
+
<label className="block mb-2 text-sm font-medium">Name</label>
|
|
128
|
+
<input
|
|
129
|
+
type="text"
|
|
130
|
+
value={name}
|
|
131
|
+
onChange={(e) => setName(e.target.value)}
|
|
132
|
+
className="w-full px-4 py-2 rounded-full border border-[var(--border)] bg-transparent"
|
|
133
|
+
placeholder="Enter your name"
|
|
134
|
+
required
|
|
135
|
+
/>
|
|
136
|
+
</div>
|
|
137
|
+
<div>
|
|
138
|
+
<label className="block mb-2 text-sm font-medium">Email</label>
|
|
139
|
+
<input
|
|
140
|
+
type="email"
|
|
141
|
+
value={email}
|
|
142
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
143
|
+
className="w-full px-4 py-2 rounded-full border border-[var(--border)] bg-transparent"
|
|
144
|
+
placeholder="Enter your email"
|
|
145
|
+
required
|
|
146
|
+
/>
|
|
147
|
+
</div>
|
|
148
|
+
<div className="flex gap-3 justify-end pt-4">
|
|
149
|
+
<button
|
|
150
|
+
type="button"
|
|
151
|
+
onClick={() => setOpen(false)}
|
|
152
|
+
className="px-6 py-2 rounded-full border border-[var(--border)] hover:bg-[var(--muted)] transition-colors"
|
|
153
|
+
>
|
|
154
|
+
Cancel
|
|
155
|
+
</button>
|
|
156
|
+
<Button type="submit">Submit</Button>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</form>
|
|
160
|
+
</Modal>
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export const ConfirmationDialog: Story = {
|
|
167
|
+
args: { open: false },
|
|
168
|
+
render: () => {
|
|
169
|
+
const [open, setOpen] = useState(false);
|
|
170
|
+
|
|
171
|
+
const handleConfirm = () => {
|
|
172
|
+
alert("Action confirmed!");
|
|
173
|
+
setOpen(false);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<div className="p-8">
|
|
178
|
+
<Button variant="primary" onClick={() => setOpen(true)}>
|
|
179
|
+
Delete Item
|
|
180
|
+
</Button>
|
|
181
|
+
<Modal
|
|
182
|
+
open={open}
|
|
183
|
+
onClose={() => setOpen(false)}
|
|
184
|
+
title="Confirm Deletion"
|
|
185
|
+
closeOnBackdrop={false}
|
|
186
|
+
>
|
|
187
|
+
<Typo variant="body-md">
|
|
188
|
+
Are you sure you want to delete this item? This action cannot be undone.
|
|
189
|
+
</Typo>
|
|
190
|
+
<div className="flex gap-3 justify-end mt-6">
|
|
191
|
+
<button
|
|
192
|
+
onClick={() => setOpen(false)}
|
|
193
|
+
className="px-6 py-2 rounded-full border border-[var(--border)] hover:bg-[var(--muted)] transition-colors"
|
|
194
|
+
>
|
|
195
|
+
Cancel
|
|
196
|
+
</button>
|
|
197
|
+
<Button variant="primary" onClick={handleConfirm}>
|
|
198
|
+
Delete
|
|
199
|
+
</Button>
|
|
200
|
+
</div>
|
|
201
|
+
</Modal>
|
|
202
|
+
</div>
|
|
203
|
+
);
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* This story demonstrates the Card component's decorative elements.
|
|
209
|
+
* The Card inside the Modal includes subtle ellipse gradients and border accents
|
|
210
|
+
* that can be toggled on/off via the `showDecorations` prop.
|
|
211
|
+
*/
|
|
212
|
+
export const WithCardDecorations: Story = {
|
|
213
|
+
args: { open: false },
|
|
214
|
+
render: () => {
|
|
215
|
+
const [open, setOpen] = useState(false);
|
|
216
|
+
const [showDecorations, setShowDecorations] = useState(true);
|
|
217
|
+
|
|
218
|
+
const ModalDemo = ({ children, ...props }: any) => (
|
|
219
|
+
<>
|
|
220
|
+
<button
|
|
221
|
+
onClick={() => setOpen(true)}
|
|
222
|
+
className="px-6 py-3 rounded-full bg-[var(--primary)] text-white hover:opacity-90 transition-opacity"
|
|
223
|
+
type="button"
|
|
224
|
+
>
|
|
225
|
+
Open Modal with Card Decorations
|
|
226
|
+
</button>
|
|
227
|
+
<label className="flex items-center gap-2 mt-4">
|
|
228
|
+
<input
|
|
229
|
+
type="checkbox"
|
|
230
|
+
checked={showDecorations}
|
|
231
|
+
onChange={(e) => setShowDecorations(e.target.checked)}
|
|
232
|
+
/>
|
|
233
|
+
<span>Show Card Decorations</span>
|
|
234
|
+
</label>
|
|
235
|
+
<Modal
|
|
236
|
+
open={open}
|
|
237
|
+
onClose={() => setOpen(false)}
|
|
238
|
+
showDecorations={showDecorations}
|
|
239
|
+
{...props}
|
|
240
|
+
/>
|
|
241
|
+
</>
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<ModalDemo title="Card with Decorative Elements">
|
|
246
|
+
<div className="space-y-4">
|
|
247
|
+
<Typo variant="body-md">
|
|
248
|
+
The Card component now includes decorative SVG elements from your Figma design:
|
|
249
|
+
</Typo>
|
|
250
|
+
<ul className="list-disc pl-6 space-y-2">
|
|
251
|
+
<li>
|
|
252
|
+
<Typo variant="body-sm">
|
|
253
|
+
<strong>Ellipse gradients</strong> - Subtle blue and white ellipses that add depth
|
|
254
|
+
</Typo>
|
|
255
|
+
</li>
|
|
256
|
+
<li>
|
|
257
|
+
<Typo variant="body-sm">
|
|
258
|
+
<strong>Gradient borders</strong> - Green gradient lines on all four sides
|
|
259
|
+
</Typo>
|
|
260
|
+
</li>
|
|
261
|
+
<li>
|
|
262
|
+
<Typo variant="body-sm">
|
|
263
|
+
<strong>Toggle option</strong> - Use the checkbox above to toggle decorations on/off
|
|
264
|
+
</Typo>
|
|
265
|
+
</li>
|
|
266
|
+
</ul>
|
|
267
|
+
<Typo variant="body-sm" className="text-gray-500">
|
|
268
|
+
Note: The current Modal is using showDecorations={showDecorations.toString()}
|
|
269
|
+
</Typo>
|
|
270
|
+
</div>
|
|
271
|
+
</ModalDemo>
|
|
272
|
+
);
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
|