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.
@@ -0,0 +1,149 @@
1
+ import { useState } from "react";
2
+
3
+ export function CheckBox(contract = {}) {
4
+
5
+ const [inputSignal, layerSignal, dataSignal, stateSignal] = [{ ...contract }, {}, {}, {}];
6
+ const checkboxId = `checkbox-${Math.random().toString(36).substring(2, 9)}`;
7
+
8
+ const layer = (name, scope = "checkbox") => (className) =>
9
+ (layerSignal[scope] ||= {},
10
+ layerSignal[scope][name] ||= [],
11
+ (layerSignal[scope][name][0] = className));
12
+
13
+ const data = (name, key = name) =>
14
+ inputSignal[key] !== undefined && (dataSignal[name] = inputSignal[key]);
15
+
16
+ const state = (name, priority = 0, initial = false) => (
17
+ (stateSignal._hooks ||= {})[name] ||= (() => {
18
+ const [get, set] = useState(initial);
19
+ return { get, set };
20
+ })(),
21
+ priority &&
22
+ (!stateSignal._priority || priority > stateSignal._priority) &&
23
+ (stateSignal[name] = stateSignal._hooks[name],
24
+ stateSignal._priority = priority)
25
+ );
26
+
27
+ const classes = (layers = {}) =>
28
+ Object.values(layers).map(l => l[0]).filter(Boolean).join(" ");
29
+
30
+ let container, checkbox, label;
31
+
32
+ container = {
33
+ base: layer("base", "container"),
34
+ color: layer("color", "container"),
35
+ size: layer("size", "container"),
36
+ layout: layer("layout", "container")
37
+ }
38
+
39
+ checkbox = {
40
+ base: layer("base", "checkbox"),
41
+ color: layer("color", "checkbox"),
42
+ size: layer("size", "checkbox"),
43
+ layout: layer("layout", "checkbox"),
44
+ checked: layer("checked", "checkbox"),
45
+ border: layer("border", "checkbox"),
46
+ shape: layer("shape", "checkbox")
47
+ }
48
+
49
+ label = {
50
+ base: layer("base", "label"),
51
+ color: layer("color", "label"),
52
+ font: layer("font", "label"),
53
+ size: layer("size", "label"),
54
+ layout: layer("layout", "label")
55
+ }
56
+
57
+ container.base("relative");
58
+ container.color("bg-transparent");
59
+ container.size("h-3 w-3");
60
+ container.layout("flex flex-row justify-center items-center");
61
+
62
+ checkbox.base("absolute inset-0 appearance-none cursor-pointer");
63
+ checkbox.color("border-gray-300 bg-transparent");
64
+ checkbox.size("h-3 w-3 checked:after:text-[8px]");
65
+ checkbox.border("border-2");
66
+ checkbox.shape("rounded-sm");
67
+ checkbox.layout("transition-all duration-200");
68
+ checkbox.checked(" checked:after:leading-none checked:border-blue-600 checked:bg-gray-300 checked:after:content-['✓'] checked:after:absolute checked:after:top-1/2 checked:after:left-1/2 checked:after:-translate-x-1/2 checked:after:-translate-y-1/2");
69
+
70
+ label.base("absolute cursor-pointer");
71
+ label.color("text-gray-800");
72
+ label.font("font-light");
73
+ label.size("text-sm");
74
+ label.layout("left-5");
75
+
76
+
77
+ inputSignal.primary && checkbox.color("border-blue-600 checked:bg-blue-50 checked:border-blue-600");
78
+ inputSignal.secondary && checkbox.color("border-gray-600 checked:bg-gray-50 checked:border-gray-600");
79
+ inputSignal.danger && checkbox.color("border-red-600 checked:bg-red-50 checked:border-red-600");
80
+ inputSignal.success && checkbox.color("border-green-600 checked:bg-green-50 checked:border-green-600");
81
+ inputSignal.warning && checkbox.color("border-yellow-600 checked:bg-yellow-50 checked:border-yellow-600");
82
+ inputSignal.info && checkbox.color("border-cyan-600 checked:bg-cyan-50 checked:border-cyan-600");
83
+
84
+ inputSignal.square && checkbox.shape("rounded-none");
85
+ inputSignal.rounded && checkbox.shape("rounded-sm");
86
+ inputSignal.pill && checkbox.shape("rounded-full");
87
+
88
+ inputSignal.hoverScale && checkbox.layout("hover:scale-105");
89
+ inputSignal.pressShrink && checkbox.layout("active:scale-95");
90
+ inputSignal.focusRing && checkbox.layout("focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1");
91
+ inputSignal.noTransition && checkbox.layout("transition-none");
92
+
93
+ inputSignal.loading && checkbox.color("opacity-50 cursor-wait");
94
+ inputSignal.readonly && checkbox.color("cursor-not-allowed opacity-60");
95
+ inputSignal.required && label.color("text-gray-900 font-medium");
96
+
97
+ inputSignal.borderNone && checkbox.border("border-0");
98
+ inputSignal.borderThick && checkbox.border("border-4");
99
+ inputSignal.borderDashed && checkbox.border("border-2 border-dashed");
100
+
101
+ inputSignal.xs && (label.size("text-xs"), container.size("h-2 w-2"), checkbox.size("h-2 w-2 checked:after:text-[4px]"));
102
+ inputSignal.sm && (label.size("text-sm"), container.size("h-3 w-3"), checkbox.size("h-3 w-3 checked:after:text-[8px]"));
103
+ inputSignal.md && (label.size("text-md"), container.size("h-4 w-4"), checkbox.size("h-4 w-4 checked:after:text-[10px]"));
104
+ inputSignal.lg && (label.size("text-lg"), container.size("h-5 w-5"), checkbox.size("h-5 w-5 checked:after:text-[12px]"));
105
+
106
+ inputSignal.labelLeft && label.layout("left-5");
107
+ inputSignal.labelRight && label.layout("right-5");
108
+ inputSignal.labelBottom && label.layout("top-5");
109
+ inputSignal.labelTop && label.layout("bottom-5");
110
+
111
+ inputSignal.label && data("label");
112
+ inputSignal.checked && data("checked");
113
+ inputSignal.defaultChecked && data("defaultChecked");
114
+ inputSignal.onChange && data("onChange");
115
+ inputSignal.disabled && data("disabled");
116
+ inputSignal.ariaLabel && data("ariaLabel");
117
+ inputSignal.ariaLabelledBy && data("ariaLabelledBy");
118
+ inputSignal.ariaDescribedBy && data("ariaDescribedBy");
119
+ inputSignal.ariaInvalid && data("ariaInvalid");
120
+ state("checked", 1, 0);
121
+
122
+ return (
123
+ <div className={classes(layerSignal.container)}>
124
+
125
+ <input
126
+ type="checkbox"
127
+ id={checkboxId}
128
+ checked={dataSignal.checked ?? stateSignal.checked?.get}
129
+ defaultChecked={dataSignal.defaultChecked}
130
+ onChange={(e) => {
131
+ const v = e.target.checked;
132
+ stateSignal.checked?.set(v);
133
+ dataSignal.onChange?.(v);
134
+ }}
135
+ disabled={dataSignal.disabled}
136
+ aria-label={dataSignal.ariaLabel}
137
+ aria-labelledby={dataSignal.ariaLabelledBy}
138
+ aria-invalid={dataSignal.ariaInvalid}
139
+ aria-describedby={dataSignal.ariaDescribedBy}
140
+ className={`peer ${classes(layerSignal.checkbox)}`}
141
+ />
142
+
143
+
144
+
145
+ {dataSignal.label && (<label htmlFor={checkboxId} className={classes(layerSignal.label)}>{dataSignal.label}</label>)}
146
+ </div>
147
+ )
148
+
149
+ }
@@ -0,0 +1,217 @@
1
+ import { Button } from "./Button";
2
+ import { useState } from "react";
3
+
4
+ export function Dropdown(contract = {}) {
5
+ /* ────────────────────────────────────────────────────────────────────────────
6
+ * CONTRACT
7
+ * ────────────────────────────────────────────────────────────────────────────
8
+ *
9
+ * Foundation: Button trigger with dropdown menu list
10
+ *
11
+ * Signals:
12
+ * Position: top, bottom, left, right
13
+ * Align: start, center, end
14
+ * Size: xs, sm, md, lg
15
+ * Layout: inline, block, centered
16
+ * State: disabled
17
+ * Interaction: reactClick, reactHover
18
+ *
19
+ * Data:
20
+ * menuName - Text for trigger button
21
+ * items - Array of {label, onClick, href?} objects
22
+ * onItemSelect - Callback when item selected
23
+ * onMenuClick - Callback when menu clicked
24
+ *
25
+ * Defaults: bottom, start, sm, inline, reactClick
26
+ *
27
+ * Usage:
28
+ * <Dropdown menuName="Menu" items={[{label:"Item 1", onClick:() => {}}]} />
29
+ *
30
+ * ──────────────────────────────────────────────────────────────────────────── */
31
+
32
+ const [inputSignal, layerSignal, dataSignal, stateSignal] = [{ ...contract }, {}, {}, {}];
33
+
34
+ /* ────────────────────────────────────────────────────────────────────────────
35
+ * CONTRACT TOOLS
36
+ * ──────────────────────────────────────────────────────────────────────────── */
37
+ const layer = (name,scope = "root") => (className) =>
38
+ (layerSignal[scope] ||= {},
39
+ layerSignal[scope][name] ||= [],
40
+ (layerSignal[scope][name][0] = className));
41
+
42
+ const data = (name, key = name) =>
43
+ inputSignal[key] && (dataSignal[name] = inputSignal[key]);
44
+
45
+ const state = (name, priority = 0) => (
46
+ (stateSignal._hooks ||= {})[name] ||= (() => { const [get, set] = useState(false); return ({ get, set }); })(),
47
+ priority && (!stateSignal._priority || priority > stateSignal._priority) && (stateSignal[name] = stateSignal._hooks[name], stateSignal._priority = priority)
48
+ );
49
+
50
+ const classes = (layers = {}) =>
51
+ Object.values(layers).map(l => l[0]).filter(Boolean).join(" ");
52
+
53
+ /* ────────────────────────────────────────────────────────────────────────────
54
+ * BASE LAYERS
55
+ * ──────────────────────────────────────────────────────────────────────────── */
56
+ let root, trigger, menu, item;
57
+ (() =>
58
+ (
59
+ root = {
60
+ base: layer("base", "root"),
61
+ layout: layer("layout", "root")
62
+ }
63
+ )
64
+ )(),
65
+ (() =>
66
+ (
67
+ trigger = {
68
+ base: layer("base", "trigger"),
69
+ layout: layer("layout", "trigger")
70
+ }
71
+ )
72
+ )(),
73
+ (() =>
74
+ (
75
+ menu = {
76
+ base: layer("base", "menu"),
77
+ size: layer("size", "menu"),
78
+ layout: layer("layout", "menu"),
79
+ shape: layer("shape", "menu"),
80
+ shadow: layer("shadow", "menu"),
81
+ color: layer("color", "menu"),
82
+ position: layer("position", "menu"),
83
+ animation: layer("animation", "menu"),
84
+ visibility: layer("visibility", "menu")
85
+ }
86
+ )
87
+ )(),
88
+ (() =>
89
+ (
90
+ item = {
91
+ base: layer("base", "item"),
92
+ hover: layer("hover", "item")
93
+ }
94
+ )
95
+ )(),
96
+ /* ────────────────────────────────────────────────────────────────────────────
97
+ * DEFAULTS
98
+ * ──────────────────────────────────────────────────────────────────────────── */
99
+ (() =>
100
+ (
101
+ root.base("relative"),
102
+ root.layout("inline-block"),
103
+ trigger.base("cursor-pointer select-none"),
104
+ menu.base("absolute z-50"),
105
+ menu.layout("flex flex-col"),
106
+ menu.shape("rounded-lg"),
107
+ menu.shadow("shadow-lg"),
108
+ menu.color("bg-white/90 text-gray-800"),
109
+ menu.visibility("opacity-100 pointer-events-auto"),
110
+ menu.animation("transition-all duration-200"),
111
+ menu.position("top-full left-0 mt-2"),
112
+ menu.size("min-w-40 text-sm"),
113
+ item.base("px-4 py-2 whitespace-nowrap"),
114
+ item.hover("hover:bg-gray-100 hover:rounded-lg cursor-pointer")
115
+ )
116
+ )(),
117
+ /* ────────────────────────────────────────────────────────────────────────────
118
+ * MENU POSITION SIGNALS
119
+ * ──────────────────────────────────────────────────────────────────────────── */
120
+ (()=>
121
+ (
122
+ inputSignal.bottom && menu.position("top-full left-0 mt-2"),
123
+ inputSignal.top && menu.position("bottom-full left-0 mb-2"),
124
+ inputSignal.right && menu.position("left-full top-0 ml-2"),
125
+ inputSignal.left && menu.position("right-full top-0 mr-2"),
126
+ inputSignal.start && menu.position("left-0"),
127
+ inputSignal.center && menu.position("left-1/2 -translate-x-1/2"),
128
+ inputSignal.end && menu.position("right-0")
129
+ )
130
+ )(),
131
+ /* ────────────────────────────────────────────────────────────────────────────
132
+ * MENU SIZE SIGNALS
133
+ * ──────────────────────────────────────────────────────────────────────────── */
134
+ (()=>
135
+ (
136
+ inputSignal.xs && menu.size("min-w-28 text-xs"),
137
+ inputSignal.sm && menu.size("min-w-36 text-sm"),
138
+ inputSignal.md && menu.size("min-w-44 text-base"),
139
+ inputSignal.lg && menu.size("min-w-56 text-lg"),
140
+ inputSignal.xl && menu.size("min-w-64 text-xl")
141
+ )
142
+ )(),
143
+ /* ────────────────────────────────────────────────────────────────────────────
144
+ * MENU LAYOUT SIGNALS
145
+ * ──────────────────────────────────────────────────────────────────────────── */
146
+ (()=>
147
+ (
148
+ inputSignal.block && root.layout("block w-full"),
149
+ inputSignal.inline && root.layout("inline-block"),
150
+ inputSignal.centered && root.layout("mx-auto")
151
+ )
152
+ )(),
153
+ /* ────────────────────────────────────────────────────────────────────────────
154
+ * MENU VISIBILITY SIGNALS
155
+ * ──────────────────────────────────────────────────────────────────────────── */
156
+ (()=>
157
+ (
158
+ inputSignal.disabled && (
159
+ trigger.base("opacity-50 pointer-events-none"),
160
+ menu.visibility("hidden")
161
+ )
162
+ )
163
+ )(),
164
+ /* ────────────────────────────────────────────────────────────────────────────
165
+ * DATA & STATE
166
+ * ──────────────────────────────────────────────────────────────────────────── */
167
+ (()=>
168
+ (
169
+ inputSignal.items && data("items"),
170
+ inputSignal.menuName && data("menuName"),
171
+ inputSignal.onItemSelect && data("onItemSelect"),
172
+ inputSignal.onMenuClick && data("onMenuClick"),
173
+ state("reactHover", 0), state("reactClick", 0),
174
+ inputSignal.reactHover && state("reactHover", 1),
175
+ state("reactClick", 1)
176
+ )
177
+ )();
178
+ /* ────────────────────────────────────────────────────────────────────────────
179
+ * INTERNAL COMPONENTS
180
+ * ──────────────────────────────────────────────────────────────────────────── */
181
+ const DropdownItem = ({ item }) => (
182
+ <div
183
+ className={classes(layerSignal.item)}
184
+ onClick={() => {
185
+ stateSignal.reactClick?.set && stateSignal.reactClick.set(false);
186
+ dataSignal.onItemSelect?.(item)
187
+ }}
188
+ {...(item.href ? { href: item.href, onClick: e => e.stopPropagation() } : {})}
189
+ >
190
+ {item.label}
191
+ </div>
192
+ );
193
+ /* ────────────────────────────────────────────────────────────────────────────
194
+ * RENDER
195
+ * ──────────────────────────────────────────────────────────────────────────── */
196
+
197
+ return (
198
+ <div className={classes(layerSignal.root)}>
199
+ <Button
200
+ className={classes(layerSignal.trigger)}
201
+ onClick={stateSignal.reactClick?.set && (() => stateSignal.reactClick.set(!stateSignal.reactClick.get))}
202
+ onMouseEnter={stateSignal.reactHover?.set && (() => stateSignal.reactHover.set(true))}
203
+ onMouseLeave={stateSignal.reactHover?.set && (() => stateSignal.reactHover.set(false))}
204
+ activeNone
205
+ >
206
+ {dataSignal.menuName}
207
+ </Button>
208
+
209
+ <div className={classes(layerSignal.menu)}>
210
+ {(stateSignal.reactClick?.get || stateSignal.reactHover?.get) && dataSignal.items?.map((item, i) => (
211
+ <DropdownItem key={i} item={item} />
212
+ ))}
213
+ </div>
214
+ </div>
215
+ );
216
+ }
217
+
@@ -0,0 +1,205 @@
1
+ import { Button } from "./Button";
2
+ import { useState } from "react";
3
+
4
+ export function FabMenu(input = {}) {
5
+ /* ────────────────────────────────────────────────────────────────────────────
6
+ * CONTRACT
7
+ * ────────────────────────────────────────────────────────────────────────────
8
+ * Signals:
9
+ * React: reactHover, reactClick
10
+ * Position: bottomRight, bottomLeft, topRight, topLeft
11
+ * Layout: actionsTop, actionsBottom, actionsLeft, actionsRight
12
+ * Size: sm, md, lg, xl
13
+ * State: disabled
14
+ *
15
+ * Data:
16
+ * icon Icon for the FAB button
17
+ * actions Array of { icon, label, onClick } objects
18
+ *
19
+ * Defaults: bottomRight, md, reactClick
20
+ *
21
+ * Usage: <FabMenu icon="+" actions={[{icon:"", label:"Action 1", onClick:() => {}}]} />
22
+ *
23
+ * ──────────────────────────────────────────────────────────────────────────── */
24
+
25
+ const [inputSignal, layerSignal, dataSignal, stateSignal] = [{ ...input }, {}, {}, {}];
26
+
27
+ /* ────────────────────────────────────────────────────────────────────────────
28
+ * CONTRACT TOOLS
29
+ * ──────────────────────────────────────────────────────────────────────────── */
30
+ const layer = (name, scope = "root") => (className) =>
31
+ (layerSignal[scope] ||= {},
32
+ layerSignal[scope][name] ||= [],
33
+ (layerSignal[scope][name][0] = className));
34
+
35
+ const data = (name, key = name) =>
36
+ inputSignal[key] && (dataSignal[name] = inputSignal[key]);
37
+
38
+ const classes = (layers = {}) =>
39
+ Object.values(layers).map(l => l[0]).filter(Boolean).join(" ");
40
+
41
+ const state = (name, priority = 0) => (
42
+ (stateSignal._hooks ||= {})[name] ||= (() => { const [get, set] = useState(false); return ({ get, set }); })(),
43
+ priority && (!stateSignal._priority || priority > stateSignal._priority) && (stateSignal[name] = stateSignal._hooks[name], stateSignal._priority = priority)
44
+ );
45
+ /* ────────────────────────────────────────────────────────────────────────────
46
+ * BASE LAYERS
47
+ * ──────────────────────────────────────────────────────────────────────────── */
48
+ let root, fab, actions, item;
49
+ (() =>
50
+ (
51
+ root = {
52
+ base: layer("base", "root"),
53
+ position: layer("position", "root")
54
+ }
55
+ )
56
+ )(),
57
+ (() =>
58
+ (
59
+ fab = {
60
+ base: layer("base", "fab"),
61
+ size: layer("size", "fab"),
62
+ visibility: layer("visibility", "fab")
63
+ }
64
+ )
65
+ )(),
66
+ (() =>
67
+ (
68
+ actions = {
69
+ base: layer("base", "actions"),
70
+ layout: layer("layout", "actions"),
71
+ visibility: layer("visibility", "actions"),
72
+ animation: layer("animation", "actions")
73
+ }
74
+ )
75
+ )(),
76
+ (() =>
77
+ (
78
+ item = { base: layer("base", "item") }
79
+ )
80
+ )(),
81
+ /* ────────────────────────────────────────────────────────────────────────────
82
+ * DEFAULTS
83
+ * ──────────────────────────────────────────────────────────────────────────── */
84
+ (() =>
85
+ (
86
+ root.base("fixed z-50"),
87
+ root.position("bottom-6 right-6"),
88
+ fab.base("flex items-center justify-center z-10"),
89
+ actions.base("absolute flex z-40"),
90
+ actions.layout("flex flex-col-reverse items-center absolute gap-2 bottom-full right-1/2 translate-x-1/2 mb-3 w-auto min-w-[40px]"),
91
+ actions.visibility("opacity-100 pointer-events-auto"),
92
+ actions.animation("transition-all duration-300"),
93
+ item.base("flex items-center justify-center")
94
+ )
95
+ )(),
96
+ /* ────────────────────────────────────────────────────────────────────────────
97
+ * POSITION SIGNALS
98
+ * ──────────────────────────────────────────────────────────────────────────── */
99
+ (() =>
100
+ (
101
+ inputSignal.bottomRight && root.position("bottom-6 right-6"),
102
+ inputSignal.bottomLeft && root.position("bottom-6 left-6"),
103
+ inputSignal.topRight && root.position("top-6 right-6"),
104
+ inputSignal.topLeft && root.position("top-6 left-6")
105
+ )
106
+ )(),
107
+ /* ────────────────────────────────────────────────────────────────────────────
108
+ * LAYOUT SIGNALS
109
+ * ──────────────────────────────────────────────────────────────────────────── */
110
+ (() =>
111
+ (
112
+ inputSignal.actionsTop && actions.layout("flex flex-col-reverse items-center absolute gap-2 bottom-full right-1/2 translate-x-1/2 mb-3 w-auto min-w-[40px]"),
113
+ inputSignal.actionsBottom && actions.layout("flex flex-col-reverse items-center absolute gap-2 top-full right-1/2 translate-x-1/2 mb-3 w-auto min-w-[40px]"),
114
+ inputSignal.actionsLeft && actions.layout("flex flex-col items-center absolute gap-2 right-full top-0 mr-2"),
115
+ inputSignal.actionsRight && actions.layout("flex flex-col items-center absolute gap-2 left-full top-0 ml-2")
116
+ )
117
+ )(),
118
+ /* ────────────────────────────────────────────────────────────────────────────
119
+ * SIZE SIGNALS
120
+ * ──────────────────────────────────────────────────────────────────────────── */
121
+ (() =>
122
+ (
123
+ inputSignal.sm && fab.size("w-8 h-8"),
124
+ inputSignal.md && fab.size("w-10 h-10"),
125
+ inputSignal.lg && fab.size("w-12 h-12"),
126
+ inputSignal.xl && fab.size("w-14 h-14")
127
+ )
128
+ )(),
129
+ /* ────────────────────────────────────────────────────────────────────────────
130
+ * VISIBILITY SIGNALS
131
+ * ──────────────────────────────────────────────────────────────────────────── */
132
+ (() =>
133
+ (
134
+ inputSignal.disabled && (
135
+ fab.visibility("opacity-50 pointer-events-none"),
136
+ actions.visibility("hidden")
137
+ )
138
+ )
139
+ )(),
140
+ /* ────────────────────────────────────────────────────────────────────────────
141
+ * DATA & STATE
142
+ * ──────────────────────────────────────────────────────────────────────────── */
143
+ (() =>
144
+ (
145
+ inputSignal.icon && data("icon"),
146
+ inputSignal.actions && data("actions"),
147
+ state("reactHover", 0), inputSignal.reactHover && state("reactHover", 1),
148
+ state("reactClick", 0), state("reactClick", 1)
149
+ )
150
+ )();
151
+ /* ────────────────────────────────────────────────────────────────────────────
152
+ * INTERNAL COMPONENTS
153
+ * ──────────────────────────────────────────────────────────────────────────── */
154
+ const ActionButton = ({ action }) => (
155
+ <Button
156
+ circle
157
+ sm
158
+ ghost
159
+ onClick={() => {
160
+ action.onClick();
161
+ stateSignal.reactClick?.set && stateSignal.reactClick?.set(!stateSignal.reactClick.get);
162
+ }}
163
+ aria-label={action.label}
164
+ className={classes(layerSignal.item)}
165
+ >
166
+ <span>{action.icon}</span>
167
+ </Button>
168
+ );
169
+ /* ────────────────────────────────────────────────────────────
170
+ * RENDER
171
+ * ──────────────────────────────────────────────────────────── */
172
+ return (
173
+ <div
174
+ className={classes(layerSignal.root)}
175
+ >
176
+ <div
177
+ className={classes(layerSignal.actions)}
178
+ >
179
+ {
180
+ (stateSignal.reactHover?.get || stateSignal.reactClick?.get)
181
+ && dataSignal.actions?.map((action, i) => (
182
+ <ActionButton
183
+ key={i}
184
+ action={action}
185
+ />
186
+ ))
187
+ }
188
+ </div>
189
+
190
+ <Button
191
+ circle
192
+ md
193
+ cta
194
+ aria-haspopup="true"
195
+ aria-expanded={stateSignal.reactClick?.get}
196
+ onClick={() => stateSignal.reactClick?.set && stateSignal.reactClick?.set(!stateSignal.reactClick.get)}
197
+ onMouseEnter={stateSignal.reactHover?.set && (() => stateSignal.reactHover.set(true))}
198
+ onMouseLeave={stateSignal.reactHover?.set && (() => stateSignal.reactHover.set(false))}
199
+ className={classes(layerSignal.fab)}
200
+ >
201
+ {dataSignal.icon}
202
+ </Button>
203
+ </div>
204
+ );
205
+ }