signal-layers 0.0.5

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/src/Button.jsx ADDED
@@ -0,0 +1,322 @@
1
+ import { useState, useRef } from "react";
2
+ export function Button(contract = {}) {
3
+ /* ────────────────────────────────────────────────────────────────────────────
4
+ * CONTRACT
5
+ * ────────────────────────────────────────────────────────────────────────────
6
+ * Button - Interactive button element with multiple visual styles
7
+ *
8
+ * Foundation: Semantic HTML button with layered styling system
9
+ *
10
+ * Signals:
11
+ * Style: cta, neumorphism, ghost
12
+ * Color: red, green, blue
13
+ * Size: xs, sm, md, lg, xl
14
+ * Shape: square, rounded, pill, circle
15
+ * Layout: block, inline, center
16
+ * Shadow: innerShadow, noShadow
17
+ * Border: border, noBorder, roundedBorder, pillBorder, circleBorder
18
+ * Hover: hoverEnlarge, hoverShrink, hoverLift, hoverFade, hoverBorder, hoverNone
19
+ * Active: activeShrink, activeRipple, activeExplode, activeSlide, activeNone
20
+ * Ripple: ripple
21
+ *
22
+ * Data:
23
+ * children - Button content
24
+ * disabled - Disable interaction
25
+ * onClick - Click handler
26
+ * type - Button type attribute
27
+ * aria-label - Accessibility label
28
+ * aria-haspopup - ARIA haspopup attribute
29
+ * aria-expanded - ARIA expanded state
30
+ * onMouseEnter - Mouse enter handler
31
+ * onMouseLeave - Mouse leave handler
32
+ *
33
+ * Defaults: md, inline, hoverEnlarge, activeShrink
34
+ *
35
+ * Usage:
36
+ * <Button onClick={() => {}}>Click me</Button>
37
+ * <Button cta blue lg>Primary Action</Button>
38
+ * <Button ghost hoverLift>Secondary</Button>
39
+ *
40
+ *
41
+ * ────────────────────────────────────────────────────────────────────────────
42
+ * CONTRACT TOOLS
43
+ * ──────────────────────────────────────────────────────────────────────────── */
44
+
45
+ const [inputSignal, layerSignal, dataSignal, stateSignal] = [{ ...contract }, {}, {}, {}];
46
+
47
+ const layer = (name, scope = "btn") => (className) =>
48
+ (layerSignal[scope] ||= {},
49
+ layerSignal[scope][name] ||= [],
50
+ (layerSignal[scope][name][0] = className));
51
+
52
+ const data = (name, key = name) =>
53
+ inputSignal[key] && (dataSignal[name] = inputSignal[key]);
54
+
55
+ const state = (name, priority = 0, initial = false) => (
56
+ (stateSignal._hooks ||= {})[name] ||= (() => {
57
+ const [get, set] = useState(initial);
58
+ return { get, set };
59
+ })(),
60
+ priority &&
61
+ (!stateSignal._priority || priority > stateSignal._priority) &&
62
+ (stateSignal[name] = stateSignal._hooks[name],
63
+ stateSignal._priority = priority)
64
+ );
65
+
66
+ const classes = (layers = {}) =>
67
+ Object.values(layers).map(l => l[0]).filter(Boolean).join(" ");
68
+
69
+ const rippleCounterRef = useRef(0);
70
+ const handleRippleClick = (e) => {
71
+ const rect = e.currentTarget.getBoundingClientRect();
72
+ stateSignal.ripples?.set([...(stateSignal.ripples?.get || []), {
73
+ id: ++rippleCounterRef.current,
74
+ x: e.clientX - rect.left,
75
+ y: e.clientY - rect.top
76
+ }]);
77
+ };
78
+
79
+ /* ────────────────────────────────────────────────────────────────────────────
80
+ * BASE LAYER
81
+ * ──────────────────────────────────────────────────────────────────────────── */
82
+ let btn, ripple;
83
+ (() =>
84
+ (
85
+ btn = {
86
+ size:layer("size"),
87
+ border:layer("border"),
88
+ shape:layer("shape"),
89
+ color:layer("color"),
90
+ shadow:layer("shadow"),
91
+ text:layer("text"),
92
+ hover:layer("hover"),
93
+ active:layer("active"),
94
+ layout:layer("layout"),
95
+ animation:layer("animation"),
96
+ rippleBase: layer("ripple")
97
+ },
98
+ ripple = {
99
+ base: layer("base", "ripple"),
100
+ animation: layer("animation", "ripple")
101
+ }
102
+ )
103
+ )(),
104
+ /* ────────────────────────────────────────────────────────────────────────────
105
+ * DEFAULT
106
+ * ──────────────────────────────────────────────────────────────────────────── */
107
+ (() =>
108
+ (
109
+ btn.size("px-4 py-2"),
110
+ btn.border("border-0"),
111
+ btn.shape("rounded-xs"),
112
+ btn.color("bg-gray-800 text-white"),
113
+ btn.shadow("shadow-xs shadow-black/50"),
114
+ btn.text("text-xs font-light font-sans"),
115
+ btn.hover("hover:scale-105 hover:shadow-md"),
116
+ btn.active("active:scale-90 active:shadow-md"),
117
+ btn.layout("flex items-center justify-center"),
118
+ btn.animation("transition-all duration-300 cursor-pointer"),
119
+ btn.rippleBase("relative overflow-hidden"),
120
+ ripple.base("absolute bg-white/30 rounded-full pointer-events-none"),
121
+ ripple.animation("-translate-x-1/2 -translate-y-1/2 transition-all")
122
+ )
123
+ )();
124
+ /* ────────────────────────────────────────────────────────────────────────────
125
+ * CALL TO ACTION COMPOSITE SIGNAL
126
+ * ──────────────────────────────────────────────────────────────────────────── */
127
+ (()=>
128
+ (
129
+ inputSignal.cta && (
130
+ btn.color("bg-blue-600/95 text-white"),
131
+ btn.shadow("shadow-xs shadow-blue-500/40"),
132
+ btn.hover("hover:scale-105 hover:shadow-md"),
133
+ btn.active("active:scale-90 active:shadow-md")
134
+ )
135
+ )
136
+ )(),
137
+ /* ────────────────────────────────────────────────────────────────────────────
138
+ * NEUMORPHIC COMPOSITE SIGNAL
139
+ * ──────────────────────────────────────────────────────────────────────────── */
140
+ (()=>
141
+ (
142
+ inputSignal.neumorphism && (
143
+ btn.shape("rounded-md"),
144
+ btn.shadow("shadow-inner"),
145
+ btn.color("bg-linear-to-r from-gray-300 to-gray-200 text-gray-800"),
146
+ btn.hover("hover:scale-105 hover:bg-white/50 hover:shadow-inner hover:shadow-gray-500/30"),
147
+ btn.active("active:scale-95 active:bg-white/50 active:shadow-inner active:shadow-gray-500/30"),
148
+ btn.animation("transition-all duration-700 cursor-pointer")
149
+ )
150
+ )
151
+ )(),
152
+ /* ────────────────────────────────────────────────────────────────────────────
153
+ * GHOST COMPOSITE SIGNAL
154
+ * ──────────────────────────────────────────────────────────────────────────── */
155
+ (()=>
156
+ (
157
+ inputSignal.ghost && (
158
+ btn.shape("rounded-full"),
159
+ btn.shadow("shadow-none"),
160
+ btn.color("bg-transparent text-gray-800"),
161
+ btn.hover("hover:scale-110 hover:bg-gray-100/50 hover:text-gray-900"),
162
+ btn.active("active:scale-95 active:bg-gray-100/75 active:text-gray-900"),
163
+ btn.animation("transition-all duration-500 cursor-pointer")
164
+ )
165
+ )
166
+ )(),
167
+ /* ────────────────────────────────────────────────────────────────────────────
168
+ * COLOR SIGNALS
169
+ * ──────────────────────────────────────────────────────────────────────────── */
170
+ (()=>
171
+ (
172
+ inputSignal.red && btn.color("bg-red-600 text-white"),
173
+ inputSignal.green && btn.color("bg-green-500 text-white"),
174
+ inputSignal.blue && btn.color("bg-blue-500/90 text-neutral-100")
175
+ )
176
+ )(),
177
+ /* ────────────────────────────────────────────────────────────────────────────
178
+ * SIZE SIGNALS
179
+ * ──────────────────────────────────────────────────────────────────────────── */
180
+ (()=>
181
+ (
182
+ inputSignal.xs && btn.size("px-2 py-1 text-xs"),
183
+ inputSignal.sm && btn.size("px-3 py-1.5 text-sm"),
184
+ inputSignal.md && btn.size("px-4 py-2 text-base"),
185
+ inputSignal.lg && btn.size("px-6 py-3 text-lg"),
186
+ inputSignal.xl && btn.size("px-8 py-4 text-xl")
187
+ )
188
+ )(),
189
+ /* ────────────────────────────────────────────────────────────────────────────
190
+ * SHAPE SIGNALS
191
+ * ──────────────────────────────────────────────────────────────────────────── */
192
+ (()=>
193
+ (
194
+ inputSignal.square && btn.shape("rounded-none"),
195
+ inputSignal.rounded && btn.shape("rounded-lg"),
196
+ inputSignal.pill && btn.shape("rounded-full"),
197
+ inputSignal.circle && btn.shape("rounded-full aspect-square p-0")
198
+ )
199
+ )(),
200
+ /* ────────────────────────────────────────────────────────────────────────────
201
+ * LAYOUT SIGNALS
202
+ * ──────────────────────────────────────────────────────────────────────────── */
203
+ (()=>
204
+ (
205
+ inputSignal.block && btn.layout("w-full"),
206
+ inputSignal.inline && btn.layout("inline-flex"),
207
+ inputSignal.center && btn.layout("mx-auto")
208
+ )
209
+ )(),
210
+ /* ────────────────────────────────────────────────────────────────────────────
211
+ * SHADOW SIGNALS
212
+ * ──────────────────────────────────────────────────────────────────────────── */
213
+ (()=>(
214
+ (
215
+ inputSignal.innerShadow && btn.shadow("shadow-inner"),
216
+ inputSignal.noShadow && btn.shadow("shadow-none")
217
+ )
218
+ ))(),
219
+ /* ────────────────────────────────────────────────────────────────────────────
220
+ * BORDER SIGNALS
221
+ * ──────────────────────────────────────────────────────────────────────────── */
222
+ (()=>
223
+ (
224
+ inputSignal.border && btn.border("border border-gray-800"),
225
+ inputSignal.noBorder && btn.border("border-none"),
226
+ inputSignal.roundedBorder && btn.border("rounded-lg"),
227
+ inputSignal.pillBorder && btn.border("rounded-full"),
228
+ inputSignal.circleBorder && btn.border("rounded-full aspect-square p-0")
229
+ )
230
+ )(),
231
+ /* ────────────────────────────────────────────────────────────────────────────
232
+ * HOVER SIGNALS
233
+ * ──────────────────────────────────────────────────────────────────────────── */
234
+ (()=>(
235
+ (
236
+ inputSignal.hoverEnlarge && btn.hover("hover:scale-105"),
237
+ inputSignal.hoverShrink && btn.hover("hover:scale-95"),
238
+ inputSignal.hoverLift && btn.hover("hover:-translate-y-0.5"),
239
+ inputSignal.hoverFade && btn.hover("hover:opacity-40"),
240
+ inputSignal.hoverBorder && btn.hover("hover:border hover:border-black"),
241
+ inputSignal.hoverNone && btn.hover("hover:scale-100 hover:opacity-100")
242
+ )
243
+ )(),
244
+ /* ────────────────────────────────────────────────────────────────────────────
245
+ * ACTIVE SIGNALS
246
+ * ──────────────────────────────────────────────────────────────────────────── */
247
+ (()=>
248
+ (
249
+ inputSignal.activeShrink && btn.active("active:scale-95 transition-transform"),
250
+ inputSignal.activeRipple && btn.active("active:ring-4 active:ring-black active:scale-90"),
251
+ inputSignal.activeExplode && btn.active("active:scale-110 active:ring-8 active:ring-black"),
252
+ inputSignal.activeSlide && btn.active("active:translate-x-0.5"),
253
+ inputSignal.activeNone && btn.active("active:scale-100 active:opacity-100")
254
+ )
255
+ )(),
256
+ /* ────────────────────────────────────────────────────────────────────────────
257
+ * DATA
258
+ * ──────────────────────────────────────────────────────────────────────────── */
259
+ (()=>
260
+ (
261
+ inputSignal.children && data("children"),
262
+ inputSignal.disabled && data("disabled"),
263
+ inputSignal.onClick && data("onClick"),
264
+ inputSignal.type && data("type"),
265
+ inputSignal["aria-label"] && data("aria-label"),
266
+ inputSignal["aria-haspopup"] && data("aria-haspopup"),
267
+ inputSignal["aria-expanded"] && data("aria-expanded"),
268
+ inputSignal.onMouseEnter && data("onMouseEnter"),
269
+ inputSignal.onMouseLeave && data("onMouseLeave"),
270
+ state("ripples", 0, []), inputSignal.ripple && data("ripple") && state("ripples", 1, [])
271
+ )
272
+ ))();
273
+
274
+ const rippleStyles = ` @keyframes ripple {
275
+ 0% { width: 0; height: 0; opacity: 0.5; }
276
+ 100% { width: 400px; height: 400px; opacity: 0; }
277
+ }
278
+ `;
279
+ /* ────────────────────────────────────────────────────────────────────────────
280
+ * RENDER
281
+ * ──────────────────────────────────────────────────────────────────────────── */
282
+
283
+ return (
284
+ <>
285
+ <style>
286
+ {rippleStyles}
287
+ </style>
288
+
289
+ <button
290
+ type={dataSignal.type}
291
+ onClick={(e) => {
292
+ dataSignal.onClick?.(e);
293
+ dataSignal.ripple && handleRippleClick(e);
294
+ }}
295
+ disabled={dataSignal.disabled}
296
+ aria-label={dataSignal["aria-label"]}
297
+ aria-haspopup={dataSignal["aria-haspopup"]}
298
+ aria-expanded={dataSignal["aria-expanded"]}
299
+ onMouseEnter={dataSignal.onMouseEnter}
300
+ onMouseLeave={dataSignal.onMouseLeave}
301
+ className={classes(layerSignal.btn)}
302
+ >
303
+
304
+ {dataSignal.children}
305
+
306
+ {dataSignal.ripple && (stateSignal.ripples?.get || [] ).map(ripple => (
307
+ <span
308
+ key={ripple.id}
309
+ className={classes(layerSignal.ripple)}
310
+ style={{
311
+ left: ripple.x,
312
+ top: ripple.y,
313
+ animation: 'ripple 2s ease-out'
314
+ }}
315
+ />
316
+ ))}
317
+
318
+ </button>
319
+ </>
320
+ );
321
+ }
322
+
package/src/Card.jsx ADDED
@@ -0,0 +1,277 @@
1
+ import {Button} from "./Button";
2
+
3
+ export function Card(contract = {}) {
4
+ /* ────────────────────────────────────────────────────────────────────────────
5
+ * CONTRACT
6
+ * ────────────────────────────────────────────────────────────────────────────
7
+ *
8
+ * Card - Content container with optional image, text, and action button
9
+ *
10
+ * Foundation: Structured layout with image, body, and action sections
11
+ *
12
+ * Signals:
13
+ * Size: xs, sm, md, lg, xl
14
+ * Layout: centered, rightAligned, leftAligned
15
+ * Image: imageCircle, imageLandscape
16
+ * Style: transparent
17
+ * Interaction: interactive
18
+ *
19
+ * Data:
20
+ * image - Image URL or source
21
+ * imageName - Alt text for image
22
+ * title - Card title text
23
+ * description - Card description text
24
+ * buttonLabel - Button text
25
+ * onButtonClick - Button click handler
26
+ *
27
+ * Defaults: md, leftAligned
28
+ *
29
+ * Usage:
30
+ * <Card title="Title" description="Description" />
31
+ * <Card image="url.jpg" title="Title" interactive lg />
32
+ * <Card centered transparent buttonLabel="Action" onButtonClick={() => {}} />
33
+ *
34
+ * ──────────────────────────────────────────────────────────────────────────── */
35
+
36
+ const [inputSignal, layerSignal, dataSignal] = [{ ...contract }, {}, {}];
37
+
38
+ /* ────────────────────────────────────────────────────────────────────────────
39
+ * CONTRACT TOOLS
40
+ * ──────────────────────────────────────────────────────────────────────────── */
41
+
42
+ const layer = (name, scope = "card") => (className) =>
43
+ (layerSignal[scope] ||= {},
44
+ layerSignal[scope][name] ||= [],
45
+ (layerSignal[scope][name][0] = className));
46
+
47
+ const data = (name, key = name) =>
48
+ inputSignal[key] && (dataSignal[name] = inputSignal[key]);
49
+
50
+ const classes = (layers = {}) =>
51
+ Object.values(layers).map(l => l[0]).filter(Boolean).join(" ");
52
+
53
+ /* ────────────────────────────────────────────────────────────────────────────
54
+ * BASE LAYERS
55
+ * ──────────────────────────────────────────────────────────────────────────── */
56
+ let card, image, title, description, button;
57
+ (() =>
58
+ (
59
+ card = {
60
+ base:layer("base", "card"),
61
+ layout:layer("layout", "card"),
62
+ spacing:layer("spacing", "card"),
63
+ border:layer("border", "card"),
64
+ shadow:layer("shadow", "card"),
65
+ hover:layer("hover", "card"),
66
+ animation:layer("animation","card")
67
+ }
68
+ )
69
+ )(),
70
+ (() =>
71
+ (
72
+ image = {
73
+ base:layer("base", "image"),
74
+ size:layer("size", "image"),
75
+ aspect:layer("aspect", "image")
76
+ }
77
+ )
78
+ )(),
79
+ (() =>
80
+ (
81
+ title = {
82
+ base:layer("base", "title"),
83
+ size:layer("size", "title"),
84
+ layout:layer("layout", "title")
85
+ }
86
+ )
87
+ )(),
88
+ (() =>
89
+ (
90
+ description = {
91
+ base:layer("base", "description"),
92
+ size:layer("size", "description"),
93
+ layout:layer("layout", "description")
94
+ }
95
+ )
96
+ )(),
97
+ (() =>
98
+ (
99
+ button = {
100
+ base:layer("base", "button")
101
+ }
102
+ )
103
+ )(),
104
+ /* ────────────────────────────────────────────────────────────────────────────
105
+ * DEFAULTS
106
+ * ──────────────────────────────────────────────────────────────────────────── */
107
+ (() =>
108
+ (
109
+ card.border("border-0"),
110
+ card.spacing("p-0 gap-4"),
111
+ card.hover("hover:scale-100"),
112
+ card.shadow("shadow-2xl shadow-gray-800/80"),
113
+ card.animation("transition-all duration-300"),
114
+ card.base("bg-gray-800 text-white rounded-lg"),
115
+ card.layout("flex flex-col justify-items-start"),
116
+ image.base("rounded-lg object-cover"),
117
+ image.size("w-full h-auto"),
118
+ image.aspect("aspect-auto"),
119
+ title.base("font-light font-sans font-stretch-condensed px-4 py-0"),
120
+ title.layout("text-left"),
121
+ title.size("text-lg"),
122
+ description.base("font-light font-sans font-stretch-condensed px-4 py-0"),
123
+ description.layout("text-left"),
124
+ description.size("text-lg"),
125
+ button.base("font-sans mb-2 cursor-pointer")
126
+ )
127
+ )(),
128
+ /* ────────────────────────────────────────────────────────────────────────────
129
+ * INTERACTIVE SIGNAL
130
+ * ──────────────────────────────────────────────────────────────────────────── */
131
+ (() =>
132
+ (
133
+ inputSignal.interactive && card.hover("cursor-pointer hover:scale-[1.02]")
134
+ )
135
+ )(),
136
+ /* ────────────────────────────────────────────────────────────────────────────
137
+ * SIZE SIGNALS
138
+ * ──────────────────────────────────────────────────────────────────────────── */
139
+ (() =>
140
+ (
141
+ inputSignal.xs && (
142
+ title.size("text-md"),
143
+ description.size("text-xs"),
144
+ card.spacing("gap-2"),
145
+ image.size("w-auto h-24")
146
+ ),
147
+
148
+ inputSignal.sm && (
149
+ title.size("text-lg"),
150
+ description.size("text-md"),
151
+ card.spacing("gap-4"),
152
+ image.size("w-auto h-32")
153
+ ),
154
+
155
+ inputSignal.md && (
156
+ title.size("text-2xl"),
157
+ description.size("text-lg"),
158
+ card.spacing("gap-6"),
159
+ image.size("w-auto h-48")
160
+ ),
161
+
162
+ inputSignal.lg && (
163
+ title.size("text-3xl"),
164
+ description.size("text-xl"),
165
+ card.spacing("gap-8"),
166
+ image.size("w-auto h-64")
167
+ ),
168
+
169
+ inputSignal.xl && (
170
+ title.size("text-4xl"),
171
+ description.size("text-2xl"),
172
+ card.spacing("gap-10"),
173
+ image.size("w-auto h-96")
174
+ )
175
+ )
176
+ )(),
177
+ /* ────────────────────────────────────────────────────────────────────────────
178
+ * LAYOUT SIGNALS
179
+ * ──────────────────────────────────────────────────────────────────────────── */
180
+ (() =>
181
+ (
182
+ inputSignal.centered && (
183
+ title.layout("text-center"),
184
+ description.layout("text-center")
185
+ ),
186
+
187
+ inputSignal.rightAligned && (
188
+ title.layout("text-right"),
189
+ description.layout("text-right")
190
+ ),
191
+
192
+ inputSignal.leftAligned && (
193
+ title.layout("text-left"),
194
+ description.layout("text-left")
195
+ ),
196
+
197
+ inputSignal.imageCircle && (
198
+ image.base("rounded-full aspect-square p-0 object-cover"),
199
+ card.layout("flex flex-col justify-center items-center")
200
+ ),
201
+
202
+ inputSignal.imageLandscape && (
203
+ image.aspect("aspect-video")
204
+ ),
205
+
206
+ inputSignal.transparent && (
207
+ card.base("bg-transparent"),
208
+ card.shadow("shadow-none")
209
+ )
210
+ )
211
+ )(),
212
+ /* ────────────────────────────────────────────────────────────────────────────
213
+ * DATA
214
+ * ──────────────────────────────────────────────────────────────────────────── */
215
+ (() =>
216
+ (
217
+ inputSignal.image && data("image"),
218
+ inputSignal.imageName && data("imageName"),
219
+ inputSignal.title && data("title"),
220
+ inputSignal.description && data("description"),
221
+ inputSignal.buttonLabel && data("buttonLabel"),
222
+ inputSignal.onButtonClick && data("onButtonClick")
223
+ )
224
+ )();
225
+ /* ──────────────────────────────────────────────────────────────────────────────
226
+ * INTERNAL COMPONENTS
227
+ * ────────────────────────────────────────────────────────────────────────────── */
228
+
229
+ const CardMedia = ({ image, imageName, layers }) =>
230
+ image ? <img src={image} className={classes(layers)} alt={imageName} loading="lazy" decoding="async"/> : null;
231
+
232
+ const CardBody = ({ title, description, titleLayers, descriptionLayers }) =>
233
+ <>{title && <h3 className={classes(titleLayers)}>{title}</h3>}{description && <p className={classes(descriptionLayers)}>{description}</p>}</>;
234
+
235
+ const CardActions = ({ label, onBtnClick, layers }) =>
236
+ <>{label && <Button rounded md border innerShadow hoverNone activeNone className={classes(layers)} onClick={onBtnClick}>{label}</Button>}</>;
237
+
238
+ const CardShell = ({ layers, children }) =>
239
+ <div className={classes(layers)}>{children}</div>;
240
+
241
+ /* ────────────────────────────────────────────────────────────────────────────
242
+ * RENDER
243
+ * ──────────────────────────────────────────────────────────────────────────── */
244
+
245
+ return (
246
+ <CardShell
247
+ layers={layerSignal.card}
248
+ >
249
+
250
+ {dataSignal.image && (
251
+ <CardMedia
252
+ image={dataSignal.image}
253
+ imageName={dataSignal.imageName}
254
+ layers={layerSignal.image}
255
+ />
256
+ )}
257
+
258
+ {(dataSignal.title || dataSignal.description) && (
259
+ <CardBody
260
+ title={dataSignal.title}
261
+ description={dataSignal.description}
262
+ titleLayers={layerSignal.title}
263
+ descriptionLayers={layerSignal.description}
264
+ />
265
+ )}
266
+
267
+ {dataSignal.buttonLabel && (
268
+ <CardActions
269
+ label={dataSignal.buttonLabel}
270
+ layers={layerSignal.button}
271
+ onBtnClick={dataSignal.onButtonClick}
272
+ />
273
+ )}
274
+
275
+ </CardShell>
276
+ );
277
+ }