@neoptocom/neopto-ui 0.9.1 → 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 +100 -36
- package/dist/index.d.cts +16 -4
- package/dist/index.d.ts +16 -4
- package/dist/index.js +100 -36
- package/package.json +1 -1
- package/src/components/Card.tsx +61 -3
- package/src/components/Modal.tsx +63 -31
- package/src/stories/Modal.stories.tsx +275 -0
package/dist/index.cjs
CHANGED
|
@@ -151,23 +151,79 @@ function Card({
|
|
|
151
151
|
children,
|
|
152
152
|
className = "",
|
|
153
153
|
style,
|
|
154
|
+
showDecorations = true,
|
|
154
155
|
...props
|
|
155
156
|
}) {
|
|
156
157
|
const mergedStyle = {
|
|
157
158
|
borderRadius: "30px",
|
|
158
|
-
|
|
159
|
+
backgroundColor: "color-mix(in srgb, var(--surface) 85%, transparent)",
|
|
159
160
|
backdropFilter: "blur(75px)",
|
|
160
161
|
WebkitBackdropFilter: "blur(75px)",
|
|
161
162
|
// Safari support
|
|
163
|
+
color: "var(--fg)",
|
|
164
|
+
position: "relative",
|
|
165
|
+
overflow: "hidden",
|
|
166
|
+
transition: "background-color 0.3s ease, color 0.3s ease",
|
|
162
167
|
...style
|
|
163
168
|
};
|
|
164
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
169
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
165
170
|
"div",
|
|
166
171
|
{
|
|
167
172
|
className: `p-6 ${className}`,
|
|
168
173
|
style: mergedStyle,
|
|
169
174
|
...props,
|
|
170
|
-
children
|
|
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
|
+
]
|
|
171
227
|
}
|
|
172
228
|
);
|
|
173
229
|
}
|
|
@@ -203,10 +259,28 @@ function Modal({
|
|
|
203
259
|
open,
|
|
204
260
|
onClose,
|
|
205
261
|
title,
|
|
206
|
-
|
|
207
|
-
children
|
|
262
|
+
closeOnBackdrop = true,
|
|
263
|
+
children,
|
|
264
|
+
className = "",
|
|
265
|
+
zIndex = 50,
|
|
266
|
+
showDecorations = true
|
|
208
267
|
}) {
|
|
209
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
|
+
}, []);
|
|
210
284
|
useIsomorphicLayoutEffect(() => {
|
|
211
285
|
setMounted(true);
|
|
212
286
|
if (!open) return;
|
|
@@ -225,40 +299,30 @@ function Modal({
|
|
|
225
299
|
return () => window.removeEventListener("keydown", onKey);
|
|
226
300
|
}, [open, onClose]);
|
|
227
301
|
if (!mounted) return null;
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
"div",
|
|
302
|
+
const modal = /* @__PURE__ */ jsxRuntime.jsx("div", { className: isDark ? "dark" : "", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
303
|
+
BackgroundBlur,
|
|
231
304
|
{
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
{
|
|
250
|
-
onClick: onClose,
|
|
251
|
-
className: "btn btn-outline",
|
|
252
|
-
type: "button",
|
|
253
|
-
children: "Close"
|
|
254
|
-
}
|
|
255
|
-
) })
|
|
256
|
-
] })
|
|
257
|
-
]
|
|
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
|
+
)
|
|
258
322
|
}
|
|
259
|
-
);
|
|
323
|
+
) });
|
|
260
324
|
const container = document.body;
|
|
261
|
-
return reactDom.createPortal(
|
|
325
|
+
return reactDom.createPortal(modal, container);
|
|
262
326
|
}
|
|
263
327
|
var typoStyles = {
|
|
264
328
|
"display-lg": { fontSize: "57px", lineHeight: "64px", letterSpacing: "-0.25px" },
|
package/dist/index.d.cts
CHANGED
|
@@ -45,21 +45,33 @@ type CardProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
|
45
45
|
children: React.ReactNode;
|
|
46
46
|
/** Additional CSS classes */
|
|
47
47
|
className?: string;
|
|
48
|
+
/** Show decorative elements (default: true) */
|
|
49
|
+
showDecorations?: boolean;
|
|
48
50
|
};
|
|
49
|
-
declare function Card({ children, className, style, ...props }: CardProps): react_jsx_runtime.JSX.Element;
|
|
51
|
+
declare function Card({ children, className, style, showDecorations, ...props }: CardProps): react_jsx_runtime.JSX.Element;
|
|
50
52
|
|
|
51
53
|
type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>;
|
|
52
54
|
declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
|
|
53
55
|
|
|
54
56
|
type ModalProps = {
|
|
57
|
+
/** Whether the modal is open */
|
|
55
58
|
open: boolean;
|
|
59
|
+
/** Callback when modal should close */
|
|
56
60
|
onClose?: () => void;
|
|
61
|
+
/** Modal content */
|
|
57
62
|
children?: React.ReactNode;
|
|
63
|
+
/** Optional title (rendered as heading) */
|
|
58
64
|
title?: string;
|
|
59
|
-
/** When true, closes when the
|
|
60
|
-
|
|
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;
|
|
61
73
|
};
|
|
62
|
-
declare function Modal({ open, onClose, title,
|
|
74
|
+
declare function Modal({ open, onClose, title, closeOnBackdrop, children, className, zIndex, showDecorations, }: ModalProps): React.ReactPortal | null;
|
|
63
75
|
|
|
64
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";
|
|
65
77
|
type TypoWeight = "normal" | "medium" | "semibold" | "bold";
|
package/dist/index.d.ts
CHANGED
|
@@ -45,21 +45,33 @@ type CardProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
|
45
45
|
children: React.ReactNode;
|
|
46
46
|
/** Additional CSS classes */
|
|
47
47
|
className?: string;
|
|
48
|
+
/** Show decorative elements (default: true) */
|
|
49
|
+
showDecorations?: boolean;
|
|
48
50
|
};
|
|
49
|
-
declare function Card({ children, className, style, ...props }: CardProps): react_jsx_runtime.JSX.Element;
|
|
51
|
+
declare function Card({ children, className, style, showDecorations, ...props }: CardProps): react_jsx_runtime.JSX.Element;
|
|
50
52
|
|
|
51
53
|
type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>;
|
|
52
54
|
declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
|
|
53
55
|
|
|
54
56
|
type ModalProps = {
|
|
57
|
+
/** Whether the modal is open */
|
|
55
58
|
open: boolean;
|
|
59
|
+
/** Callback when modal should close */
|
|
56
60
|
onClose?: () => void;
|
|
61
|
+
/** Modal content */
|
|
57
62
|
children?: React.ReactNode;
|
|
63
|
+
/** Optional title (rendered as heading) */
|
|
58
64
|
title?: string;
|
|
59
|
-
/** When true, closes when the
|
|
60
|
-
|
|
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;
|
|
61
73
|
};
|
|
62
|
-
declare function Modal({ open, onClose, title,
|
|
74
|
+
declare function Modal({ open, onClose, title, closeOnBackdrop, children, className, zIndex, showDecorations, }: ModalProps): React.ReactPortal | null;
|
|
63
75
|
|
|
64
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";
|
|
65
77
|
type TypoWeight = "normal" | "medium" | "semibold" | "bold";
|
package/dist/index.js
CHANGED
|
@@ -130,23 +130,79 @@ function Card({
|
|
|
130
130
|
children,
|
|
131
131
|
className = "",
|
|
132
132
|
style,
|
|
133
|
+
showDecorations = true,
|
|
133
134
|
...props
|
|
134
135
|
}) {
|
|
135
136
|
const mergedStyle = {
|
|
136
137
|
borderRadius: "30px",
|
|
137
|
-
|
|
138
|
+
backgroundColor: "color-mix(in srgb, var(--surface) 85%, transparent)",
|
|
138
139
|
backdropFilter: "blur(75px)",
|
|
139
140
|
WebkitBackdropFilter: "blur(75px)",
|
|
140
141
|
// Safari support
|
|
142
|
+
color: "var(--fg)",
|
|
143
|
+
position: "relative",
|
|
144
|
+
overflow: "hidden",
|
|
145
|
+
transition: "background-color 0.3s ease, color 0.3s ease",
|
|
141
146
|
...style
|
|
142
147
|
};
|
|
143
|
-
return /* @__PURE__ */
|
|
148
|
+
return /* @__PURE__ */ jsxs(
|
|
144
149
|
"div",
|
|
145
150
|
{
|
|
146
151
|
className: `p-6 ${className}`,
|
|
147
152
|
style: mergedStyle,
|
|
148
153
|
...props,
|
|
149
|
-
children
|
|
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
|
+
]
|
|
150
206
|
}
|
|
151
207
|
);
|
|
152
208
|
}
|
|
@@ -182,10 +238,28 @@ function Modal({
|
|
|
182
238
|
open,
|
|
183
239
|
onClose,
|
|
184
240
|
title,
|
|
185
|
-
|
|
186
|
-
children
|
|
241
|
+
closeOnBackdrop = true,
|
|
242
|
+
children,
|
|
243
|
+
className = "",
|
|
244
|
+
zIndex = 50,
|
|
245
|
+
showDecorations = true
|
|
187
246
|
}) {
|
|
188
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
|
+
}, []);
|
|
189
263
|
useIsomorphicLayoutEffect(() => {
|
|
190
264
|
setMounted(true);
|
|
191
265
|
if (!open) return;
|
|
@@ -204,40 +278,30 @@ function Modal({
|
|
|
204
278
|
return () => window.removeEventListener("keydown", onKey);
|
|
205
279
|
}, [open, onClose]);
|
|
206
280
|
if (!mounted) return null;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
"div",
|
|
281
|
+
const modal = /* @__PURE__ */ jsx("div", { className: isDark ? "dark" : "", children: /* @__PURE__ */ jsx(
|
|
282
|
+
BackgroundBlur,
|
|
210
283
|
{
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
{
|
|
229
|
-
onClick: onClose,
|
|
230
|
-
className: "btn btn-outline",
|
|
231
|
-
type: "button",
|
|
232
|
-
children: "Close"
|
|
233
|
-
}
|
|
234
|
-
) })
|
|
235
|
-
] })
|
|
236
|
-
]
|
|
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
|
+
)
|
|
237
301
|
}
|
|
238
|
-
);
|
|
302
|
+
) });
|
|
239
303
|
const container = document.body;
|
|
240
|
-
return createPortal(
|
|
304
|
+
return createPortal(modal, container);
|
|
241
305
|
}
|
|
242
306
|
var typoStyles = {
|
|
243
307
|
"display-lg": { fontSize: "57px", lineHeight: "64px", letterSpacing: "-0.25px" },
|
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": [
|
package/src/components/Card.tsx
CHANGED
|
@@ -5,20 +5,27 @@ export type CardProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
|
5
5
|
children: React.ReactNode;
|
|
6
6
|
/** Additional CSS classes */
|
|
7
7
|
className?: string;
|
|
8
|
+
/** Show decorative elements (default: true) */
|
|
9
|
+
showDecorations?: boolean;
|
|
8
10
|
};
|
|
9
11
|
|
|
10
12
|
export function Card({
|
|
11
13
|
children,
|
|
12
14
|
className = "",
|
|
13
15
|
style,
|
|
16
|
+
showDecorations = true,
|
|
14
17
|
...props
|
|
15
18
|
}: CardProps) {
|
|
16
19
|
// Merge user styles with card styles
|
|
17
20
|
const mergedStyle: React.CSSProperties = {
|
|
18
21
|
borderRadius: "30px",
|
|
19
|
-
|
|
22
|
+
backgroundColor: "color-mix(in srgb, var(--surface) 85%, transparent)",
|
|
20
23
|
backdropFilter: "blur(75px)",
|
|
21
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",
|
|
22
29
|
...style,
|
|
23
30
|
};
|
|
24
31
|
|
|
@@ -28,8 +35,59 @@ export function Card({
|
|
|
28
35
|
style={mergedStyle}
|
|
29
36
|
{...props}
|
|
30
37
|
>
|
|
31
|
-
{
|
|
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>
|
|
32
91
|
</div>
|
|
33
92
|
);
|
|
34
93
|
}
|
|
35
|
-
|
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
|
}
|
|
@@ -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
|
+
|