signal-layers 0.0.6 → 0.1.0

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/Slider.jsx CHANGED
@@ -1,306 +1,153 @@
1
- import { useState } from "react";
2
-
3
1
  export function Slider(contract = {}) {
4
- /* ────────────────────────────────────────────────────────────────────────────
5
- * CONTRACT
6
- * ────────────────────────────────────────────────────────────────────────────
7
- *
8
- * Slider - Continuous range input with value bubble on interaction
9
- *
10
- * Foundation: Native HTML range input with dynamic value bubble
11
- *
12
- * Signals:
13
- * Size: xs, sm, md, lg, xl
14
- * Color: primary, neutral
15
- * Shape: square
16
- * Layout: inline, block, centered
17
- * State: disabled
18
- * Bubble Size: bubbleXs, bubbleSm, bubbleMd, bubbleLg, bubbleXl
19
- * Bubble Shape: bubbleSquare, bubblePill, bubbleFlat, bubbleSharp
20
- *
21
- * Data:
22
- * defaultValue - Initial value (uncontrolled)
23
- * min - Minimum value
24
- * max - Maximum value
25
- * step - Step interval
26
- * onChange - Change handler
27
- * ariaLabel - Accessibility label
28
- *
29
- * Defaults: md, primary, block
30
- *
31
- * Usage:
32
- * <Slider defaultValue={40} onChange={v => {}} />
33
- * <Slider lg primary min={0} max={200} />
34
- * <Slider sm neutral square bubbleLg />
35
- *
36
- *
37
- * ────────────────────────────────────────────────────────────────────────────
38
- */
39
-
40
- const [inputSignal, layerSignal, dataSignal, stateSignal] = [
41
- { ...contract },
42
- {},
43
- {},
44
- {}
45
- ];
46
-
47
- /* ────────────────────────────────────────────────────────────────────────────
48
- * CONTRACT TOOLS
49
- * ──────────────────────────────────────────────────────────────────────────── */
50
-
51
- const layer = (name, scope = "card") => (className) =>
52
- (layerSignal[scope] ||= {},
53
- layerSignal[scope][name] ||= [],
54
- (layerSignal[scope][name][0] = className));
55
-
56
- const data = (name, key = name) =>
57
- inputSignal[key] !== undefined && (dataSignal[name] = inputSignal[key]);
58
-
59
- const state = (name, priority = 0, initial = 0) => (
60
- (stateSignal._hooks ||= {})[name] ||= (() => { const [get, set] = useState(initial); return ({ get, set }); })(),
61
- priority && (!stateSignal._priority || priority > stateSignal._priority) && (stateSignal[name] = stateSignal._hooks[name], stateSignal._priority = priority)
62
- );
63
-
64
- const classes = (layers = {}) =>
65
- Object.values(layers).map(l => l[0]).filter(Boolean).join(" ");
66
-
67
- /* ────────────────────────────────────────────────────────────────────────────
68
- * BASE LAYERS
69
- * ──────────────────────────────────────────────────────────────────────────── */
70
-
2
+ const { layer, data, state, classes, signals } = createSignalUtils(contract);
3
+ const { inputSignal, layerSignal, dataSignal, stateSignal } = signals;
71
4
  let container, slider, text, bubble;
72
-
73
- (() =>
74
- (
75
- container = {
76
- base:layer("base", "container")
77
- }
78
- )
79
- )();
80
-
81
- (() =>
82
- (
83
- slider = {
84
- base: layer("base", "slider"),
85
- track: layer("track", "slider"),
86
- thumb: layer("thumb", "slider"),
87
- size: layer("size", "slider"),
88
- color: layer("color", "slider"),
89
- shape: layer("shape", "slider"),
90
- layout: layer("layout", "slider"),
91
- interaction: layer("interaction", "slider")
92
- }
93
- )
94
- )();
95
-
96
- (() =>
97
- (
98
- text = {
99
- base: layer("text-base", "text"),
100
- position: layer("position", "text"),
101
- bubble: layer("bubble", "text"),
102
- color: layer("color", "text")
103
- }
104
- )
105
- )();
106
-
107
- (() =>
108
- (
109
- bubble = {
110
- base: layer("base", "bubble"),
111
- color: layer("color", "bubble"),
112
- size: layer("size", "bubble"),
113
- shape: layer("shape", "bubble")
114
- }
115
- )
116
- )();
117
-
118
- /* ────────────────────────────────────────────────────────────────────────────
119
- * DEFAULTS
120
- * ──────────────────────────────────────────────────────────────────────────── */
121
-
122
- (() =>
123
- (
124
- container.base( "inline-flex items-center select-none relative"),
125
- slider.base("appearance-none outline-none focus:outline-none transition-all duration-200"),
126
- slider.track("[&::-webkit-slider-runnable-track]:h-2 [&::-webkit-slider-runnable-track]:rounded-full [&::-moz-range-track]:h-2 [&::-moz-range-track]:rounded-full"),
127
- slider.thumb("[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:shadow-md [&::-webkit-slider-thumb]:-mt-1 [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:rounded-full"),
128
- slider.color("[&::-webkit-slider-runnable-track]:bg-gray-900 [&::-moz-range-track]:bg-gray-900"),
129
- slider.interaction("cursor-pointer"),
130
- slider.shape("rounded-full"),
131
- slider.layout("block"),
132
- slider.size("w-48"),
133
- text.base("text-gray-800 font-light font-mono text-xs absolute -top-8"),
134
- text.position("left-(--thumb-position) -translate-x-1/2 ml-(--thumb-margin)"),
135
- text.color("text-gray-800"),
136
- bubble.base("inline-block"),
137
- bubble.color("bg-white"),
138
- bubble.size("px-2 py-1"),
139
- bubble.shape("rounded shadow-md")
140
- )
141
- )();
142
-
143
- /* ────────────────────────────────────────────────────────────────────────────
144
- * SLIDER SIZE SIGNALS
145
- * ──────────────────────────────────────────────────────────────────────────── */
146
-
147
- (() =>
148
- (
149
- inputSignal.xs && slider.size("w-24"),
150
- inputSignal.sm && slider.size("w-32"),
151
- inputSignal.md && slider.size("w-48"),
152
- inputSignal.lg && slider.size("w-64"),
153
- inputSignal.xl && slider.size("w-80")
154
- )
155
- )();
156
-
157
- /* ────────────────────────────────────────────────────────────────────────────
158
- * BUBBLE SIZE SIGNALS
159
- * ──────────────────────────────────────────────────────────────────────────── */
160
-
161
- (() =>
162
- (
163
- inputSignal.bubbleXs && bubble.size("px-1 py-0.5 text-2xs"),
164
- inputSignal.bubbleSm && bubble.size("px-1.5 py-0.5 text-xs"),
165
- inputSignal.bubbleMd && bubble.size("px-2 py-1 text-xs"),
166
- inputSignal.bubbleLg && bubble.size("px-3 py-1.5 text-sm"),
167
- inputSignal.bubbleXl && bubble.size("px-4 py-2 text-base")
168
- )
169
- )();
170
-
171
- /* ────────────────────────────────────────────────────────────────────────────
172
- * BUBBLE SHAPE SIGNALS
173
- * ──────────────────────────────────────────────────────────────────────────── */
174
-
175
- (() =>
176
- (
177
- inputSignal.bubbleSquare &&
178
- bubble.shape("rounded-none shadow-sm"),
179
- inputSignal.bubblePill &&
180
- bubble.shape("rounded-full shadow-lg"),
181
- inputSignal.bubbleFlat &&
182
- bubble.shape("rounded shadow-none"),
183
- inputSignal.bubbleSharp &&
184
- bubble.shape("rounded-xl shadow-2xl")
185
- )
186
- )();
187
-
188
- /* ────────────────────────────────────────────────────────────────────────────
189
- * COLOR SIGNALS
190
- * ──────────────────────────────────────────────────────────────────────────── */
191
-
192
- (() =>
193
- (
194
- inputSignal.primary && (
195
- slider.color("[&::-webkit-slider-runnable-track]:bg-blue-500 [&::-moz-range-track]:bg-blue-500"),
196
- bubble.color("bg-blue-600"),
197
- text.color("text-white")
198
- ),
199
- inputSignal.neutral && (
200
- slider.color("[&::-webkit-slider-runnable-track]:bg-gray-400 [&::-moz-range-track]:bg-gray-400"),
201
- bubble.color("bg-gray-500"),
202
- text.color("text-white")
203
- )
204
- )
205
- )();
206
-
207
- /* ────────────────────────────────────────────────────────────────────────────
208
- * SHAPE SIGNALS
209
- * ──────────────────────────────────────────────────────────────────────────── */
210
-
211
- (() =>
212
- (
213
- inputSignal.square &&
214
- slider.shape("[&::-webkit-slider-runnable-track]:rounded-none [&::-webkit-slider-thumb]:rounded-none [&::-moz-range-track]:rounded-none [&::-moz-range-thumb]:rounded-none")
215
- )
216
- )();
217
-
218
- /* ────────────────────────────────────────────────────────────────────────────
219
- * LAYOUT SIGNALS
220
- * ──────────────────────────────────────────────────────────────────────────── */
221
-
222
- (() =>
223
- (
224
- inputSignal.inline && slider.layout("inline-block"),
225
- inputSignal.block && slider.layout("block"),
226
- inputSignal.centered && slider.layout("mx-auto")
227
- )
228
- )();
229
-
230
- /* ────────────────────────────────────────────────────────────────────────────
231
- * INTERACTION SIGNALS
232
- * ──────────────────────────────────────────────────────────────────────────── */
233
-
234
- (() =>
235
- (
236
- inputSignal.disabled &&
237
- slider.interaction("opacity-50 pointer-events-none")
238
- )
239
- )();
240
-
241
- /* ────────────────────────────────────────────────────────────────────────────
242
- * DATA & STATE
243
- * ──────────────────────────────────────────────────────────────────────────── */
244
-
245
- (() =>
246
- (
247
- inputSignal.min && data("min"),
248
- inputSignal.max && data("max"),
249
- inputSignal.step && data("step"),
250
- inputSignal.ariaLabel && data("ariaLabel"),
251
- inputSignal.onChange && data("onChange"),
252
- inputSignal.defaultValue && data("defaultValue"),
253
- state("isSliding", 1, false),
254
- state("value", 2, dataSignal.defaultValue ?? 0)
255
- )
256
- )();
257
-
258
- const thumbVars = () => {
259
- const percentage = ((stateSignal.value.get - (dataSignal.min ?? 0)) /
260
- ((dataSignal.max ?? 100) - (dataSignal.min ?? 0))) * 100;
261
- return {
262
- position: `${percentage}%`,
263
- margin: `${Math.max(0, 5 - (percentage * 5 / 100))}px`
264
- };
265
- }
266
- /* ────────────────────────────────────────────────────────────────────────────
267
- * RENDER
268
- * ──────────────────────────────────────────────────────────────────────────── */
5
+ container = {
6
+ base: layer("base", "container"),
7
+ };
8
+ slider = {
9
+ base: layer("base", "slider"),
10
+ track: layer("track", "slider"),
11
+ thumb: layer("thumb", "slider"),
12
+ size: layer("size", "slider"),
13
+ color: layer("color", "slider"),
14
+ shape: layer("shape", "slider"),
15
+ layout: layer("layout", "slider"),
16
+ interaction: layer("interaction", "slider"),
17
+ };
18
+
19
+ text = {
20
+ base: layer("text-base", "text"),
21
+ position: layer("position", "text"),
22
+ bubble: layer("bubble", "text"),
23
+ color: layer("color", "text"),
24
+ };
25
+
26
+ bubble = {
27
+ base: layer("base", "bubble"),
28
+ color: layer("color", "bubble"),
29
+ size: layer("size", "bubble"),
30
+ shape: layer("shape", "bubble"),
31
+ };
32
+
33
+ container.base("inline-flex items-center select-none relative");
34
+
35
+ slider.base("appearance-none outline-none focus:outline-none transition-all duration-200");
36
+ slider.track("[&::-webkit-slider-runnable-track]:h-2 [&::-webkit-slider-runnable-track]:rounded-full [&::-moz-range-track]:h-2 [&::-moz-range-track]:rounded-full");
37
+ slider.thumb("[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:shadow-md [&::-webkit-slider-thumb]:-mt-1 [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:rounded-full");
38
+ slider.color("[&::-webkit-slider-runnable-track]:bg-gray-900 [&::-moz-range-track]:bg-gray-900");
39
+ slider.interaction("cursor-pointer");
40
+ slider.shape("rounded-full");
41
+ slider.layout("block");
42
+ slider.size("w-48");
43
+
44
+ text.base("text-gray-800 font-light font-mono text-xs absolute -top-8");
45
+ text.position("left-(--thumb-position) -translate-x-1/2 ml-(--thumb-margin)");
46
+ text.color("text-gray-800");
47
+
48
+ bubble.base("inline-block");
49
+ bubble.color("bg-white");
50
+ bubble.size("px-2 py-1");
51
+ bubble.shape("rounded shadow-md");
52
+
53
+ inputSignal.xs && slider.size("w-24");
54
+ inputSignal.sm && slider.size("w-32");
55
+ inputSignal.md && slider.size("w-48");
56
+ inputSignal.lg && slider.size("w-64");
57
+ inputSignal.xl && slider.size("w-80");
58
+
59
+ inputSignal.bubbleXs && bubble.size("px-1 py-0.5 text-2xs");
60
+ inputSignal.bubbleSm && bubble.size("px-1.5 py-0.5 text-xs");
61
+ inputSignal.bubbleMd && bubble.size("px-2 py-1 text-xs");
62
+ inputSignal.bubbleLg && bubble.size("px-3 py-1.5 text-sm");
63
+ inputSignal.bubbleXl && bubble.size("px-4 py-2 text-base");
64
+
65
+ inputSignal.bubbleSquare && bubble.shape("rounded-none shadow-sm");
66
+ inputSignal.bubblePill && bubble.shape("rounded-full shadow-lg");
67
+ inputSignal.bubbleFlat && bubble.shape("rounded shadow-none");
68
+ inputSignal.bubbleSharp && bubble.shape("rounded-xl shadow-2xl");
69
+
70
+ inputSignal.primary &&
71
+ (slider.color("[&::-webkit-slider-runnable-track]:bg-blue-500 [&::-moz-range-track]:bg-blue-500"),
72
+ bubble.color("bg-blue-600"),
73
+ text.color("text-white"));
74
+ inputSignal.neutral &&
75
+ (slider.color("[&::-webkit-slider-runnable-track]:bg-gray-400 [&::-moz-range-track]:bg-gray-400"),
76
+ bubble.color("bg-gray-500"),
77
+ text.color("text-white"));
78
+
79
+ inputSignal.square && slider.shape("[&::-webkit-slider-runnable-track]:rounded-none [&::-webkit-slider-thumb]:rounded-none [&::-moz-range-track]:rounded-none [&::-moz-range-thumb]:rounded-none");
80
+ inputSignal.pill && slider.shape("[&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-thumb]:rounded-full [&::-moz-range-track]:rounded-full [&::-moz-range-thumb]:rounded-full");
81
+
82
+ inputSignal.inline && slider.layout("inline-block");
83
+ inputSignal.block && slider.layout("block");
84
+ inputSignal.centered && slider.layout("mx-auto");
85
+
86
+ inputSignal.disabled && slider.interaction("opacity-50 pointer-events-none");
87
+
88
+ inputSignal.min && data("min");
89
+ inputSignal.max && data("max");
90
+ inputSignal.step && data("step");
91
+ inputSignal.ariaLabel && data("ariaLabel");
92
+ inputSignal.onChange && data("onChange");
93
+ inputSignal.defaultValue && data("defaultValue");
94
+ inputSignal.value && data("value");
95
+ state("isSliding", 1, false);
96
+ state("value", 2, dataSignal.defaultValue ?? 0);
97
+
98
+ const thumbVars = () => {
99
+ const percentage =
100
+ ((stateSignal.value.get - (dataSignal.min ?? 0)) /
101
+ ((dataSignal.max ?? 100) - (dataSignal.min ?? 0))) *
102
+ 100;
103
+ return {
104
+ position: `${percentage}%`,
105
+ margin: `${Math.max(0, 5 - (percentage * 5) / 100)}px`,
106
+ };
107
+ };
269
108
 
270
109
  return (
271
- <div className={classes(layerSignal.container)}>
272
- <input
273
- type="range"
274
- min={dataSignal.min ?? 0}
275
- max={dataSignal.max ?? 100}
276
- step={dataSignal.step ?? 1}
277
- value={stateSignal.value.get}
278
- aria-label={dataSignal.ariaLabel}
279
- disabled={inputSignal.disabled}
280
- onChange={(e) => {
281
- const v = Number(e.target.value);
282
- stateSignal.value?.set && stateSignal.value.set(v);
283
- dataSignal.onChange?.(v);
284
- }}
285
- onMouseUp={() => stateSignal.isSliding?.set && stateSignal.isSliding.set(false)}
286
- onMouseDown={() => stateSignal.isSliding?.set && stateSignal.isSliding.set(true)}
287
- onTouchStart={() => stateSignal.isSliding?.set && stateSignal.isSliding.set(true)}
288
- onTouchEnd={() => stateSignal.isSliding?.set && stateSignal.isSliding.set(false)}
289
- className={classes(layerSignal.slider)}
290
- />
291
- {stateSignal.isSliding?.get && (
292
- <div
293
- className={classes(layerSignal.text)}
294
- style={{
295
- "--thumb-position": thumbVars().position,
296
- "--thumb-margin": thumbVars().margin
297
- }}
110
+ <div className={classes(layerSignal.container)}>
111
+ <input
112
+ type="range"
113
+ min={dataSignal.min ?? 0}
114
+ max={dataSignal.max ?? 100}
115
+ step={dataSignal.step ?? 1}
116
+ value={dataSignal.value ?? stateSignal.value.get}
117
+ aria-label={dataSignal.ariaLabel}
118
+ disabled={inputSignal.disabled}
119
+ onChange={(e) => {
120
+ const v = Number(e.target.value);
121
+ stateSignal.value?.set && stateSignal.value.set(v);
122
+ dataSignal.onChange?.(v);
123
+ }}
124
+ onMouseUp={() =>
125
+ stateSignal.isSliding?.set && stateSignal.isSliding.set(false)
126
+ }
127
+ onMouseDown={() =>
128
+ stateSignal.isSliding?.set && stateSignal.isSliding.set(true)
129
+ }
130
+ onTouchStart={() =>
131
+ stateSignal.isSliding?.set && stateSignal.isSliding.set(true)
132
+ }
133
+ onTouchEnd={() =>
134
+ stateSignal.isSliding?.set && stateSignal.isSliding.set(false)
135
+ }
136
+ className={classes(layerSignal.slider)}
137
+ />
138
+ {stateSignal.isSliding?.get && (
139
+ <div
140
+ className={classes(layerSignal.text)}
141
+ style={{
142
+ "--thumb-position": thumbVars().position,
143
+ "--thumb-margin": thumbVars().margin,
144
+ }}
298
145
  >
299
- <div className={classes(layerSignal.bubble)}>
300
- {Math.round(stateSignal.value.get)}</div>
146
+ <div className={classes(layerSignal.bubble)}>
147
+ {Math.round(stateSignal.value.get)}
148
+ </div>
301
149
  </div>
302
- )}
303
-
304
- </div>
150
+ )}
151
+ </div>
305
152
  );
306
153
  }