@jsenv/navi 0.6.1 → 0.7.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.
@@ -0,0 +1,506 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Flex Layout Demo</title>
6
+ <style>
7
+ .demo-box {
8
+ min-width: 60px;
9
+ padding: 10px;
10
+ text-align: center;
11
+ background: #e3f2fd;
12
+ border: 1px solid #90caf9;
13
+ border-radius: 4px;
14
+ }
15
+ .demo-section {
16
+ margin-bottom: 30px;
17
+ padding: 20px;
18
+ background: #f8f9fa;
19
+ border-radius: 8px;
20
+ }
21
+ </style>
22
+ </head>
23
+ <body>
24
+ <div id="app"></div>
25
+ <script type="module" jsenv-type="module/jsx">
26
+ /* eslint-disable no-unused-vars */
27
+ import { render } from "preact";
28
+ import { FlexRow, FlexColumn, FlexItem, Button } from "@jsenv/navi";
29
+
30
+ const DemoBox = ({ children, ...props }) => (
31
+ <div className="demo-box" {...props}>
32
+ {children}
33
+ </div>
34
+ );
35
+
36
+ const Demo = () => {
37
+ return (
38
+ <div style={{ padding: "20px", fontFamily: "system-ui, sans-serif" }}>
39
+ <h1>Flex Layout Demo</h1>
40
+
41
+ <h2>FlexRow</h2>
42
+
43
+ <section className="demo-section">
44
+ <h3>1. Basic FlexRow</h3>
45
+ <FlexRow>
46
+ <DemoBox>Item 1</DemoBox>
47
+ <DemoBox>Item 2</DemoBox>
48
+ <DemoBox>Item 3</DemoBox>
49
+ </FlexRow>
50
+ </section>
51
+
52
+ <section className="demo-section">
53
+ <h3>2. FlexRow with Gap</h3>
54
+ <FlexRow gap="15px">
55
+ <DemoBox>Item 1</DemoBox>
56
+ <DemoBox>Item 2</DemoBox>
57
+ <DemoBox>Item 3</DemoBox>
58
+ </FlexRow>
59
+ </section>
60
+
61
+ <section className="demo-section">
62
+ <h3>3. FlexRow with Vertical Alignment</h3>
63
+
64
+ <h4>alignY="start"</h4>
65
+ <FlexRow
66
+ alignY="start"
67
+ gap="10px"
68
+ style={{
69
+ height: "80px",
70
+ background: "#fff3e0",
71
+ padding: "10px",
72
+ }}
73
+ >
74
+ <DemoBox>Short</DemoBox>
75
+ <DemoBox style={{ height: "60px", lineHeight: "60px" }}>
76
+ Tall Item
77
+ </DemoBox>
78
+ <DemoBox>Short</DemoBox>
79
+ </FlexRow>
80
+
81
+ <h4>alignY="center" (default)</h4>
82
+ <FlexRow
83
+ alignY="center"
84
+ gap="10px"
85
+ style={{
86
+ height: "80px",
87
+ background: "#e8f5e8",
88
+ padding: "10px",
89
+ }}
90
+ >
91
+ <DemoBox>Short</DemoBox>
92
+ <DemoBox style={{ height: "60px", lineHeight: "60px" }}>
93
+ Tall Item
94
+ </DemoBox>
95
+ <DemoBox>Short</DemoBox>
96
+ </FlexRow>
97
+
98
+ <h4>alignY="end"</h4>
99
+ <FlexRow
100
+ alignY="end"
101
+ gap="10px"
102
+ style={{
103
+ height: "80px",
104
+ background: "#ffebee",
105
+ padding: "10px",
106
+ }}
107
+ >
108
+ <DemoBox>Short</DemoBox>
109
+ <DemoBox style={{ height: "60px", lineHeight: "60px" }}>
110
+ Tall Item
111
+ </DemoBox>
112
+ <DemoBox>Short</DemoBox>
113
+ </FlexRow>
114
+ </section>
115
+
116
+ <h2>FlexRow + FlexItem</h2>
117
+
118
+ <section className="demo-section">
119
+ <h3>4. FlexItem Basic Usage</h3>
120
+
121
+ <FlexRow gap="10px">
122
+ <FlexItem>
123
+ <DemoBox>FlexItem 1</DemoBox>
124
+ </FlexItem>
125
+ <FlexItem>
126
+ <DemoBox>FlexItem 2</DemoBox>
127
+ </FlexItem>
128
+ <FlexItem>
129
+ <DemoBox>FlexItem 3</DemoBox>
130
+ </FlexItem>
131
+ </FlexRow>
132
+ </section>
133
+
134
+ <section className="demo-section">
135
+ <h3>5. FlexItem with Growth</h3>
136
+
137
+ <FlexRow gap="10px">
138
+ <FlexItem>
139
+ <DemoBox>Fixed</DemoBox>
140
+ </FlexItem>
141
+ <FlexItem grow>
142
+ <DemoBox>Grows to fill</DemoBox>
143
+ </FlexItem>
144
+ <FlexItem>
145
+ <DemoBox>Fixed</DemoBox>
146
+ </FlexItem>
147
+ </FlexRow>
148
+ </section>
149
+
150
+ <section className="demo-section">
151
+ <h3>6. FlexItem with alignY</h3>
152
+
153
+ <FlexRow
154
+ gap="10px"
155
+ style={{
156
+ height: "100px",
157
+ background: "#e1f5fe",
158
+ padding: "10px",
159
+ }}
160
+ >
161
+ <FlexItem>
162
+ <DemoBox>Default (center)</DemoBox>
163
+ </FlexItem>
164
+ <FlexItem alignY="start">
165
+ <DemoBox>alignY="start"</DemoBox>
166
+ </FlexItem>
167
+ <FlexItem alignY="end">
168
+ <DemoBox>alignY="end"</DemoBox>
169
+ </FlexItem>
170
+ <FlexItem alignY="stretch">
171
+ <DemoBox style={{ height: "100%" }}>alignY="stretch"</DemoBox>
172
+ </FlexItem>
173
+ </FlexRow>
174
+ </section>
175
+
176
+ <section className="demo-section">
177
+ <h3>7. FlexItem with alignX</h3>
178
+
179
+ <FlexRow
180
+ gap="10px"
181
+ style={{
182
+ background: "#f3e5f5",
183
+ padding: "10px",
184
+ }}
185
+ >
186
+ <FlexItem>
187
+ <DemoBox>Default</DemoBox>
188
+ </FlexItem>
189
+ <FlexItem alignX="start">
190
+ <DemoBox>alignX="start"</DemoBox>
191
+ </FlexItem>
192
+ <FlexItem alignX="center">
193
+ <DemoBox>alignX="center"</DemoBox>
194
+ </FlexItem>
195
+ <FlexItem alignX="end">
196
+ <DemoBox>alignX="end"</DemoBox>
197
+ </FlexItem>
198
+ </FlexRow>
199
+ </section>
200
+
201
+ <section className="demo-section">
202
+ <h3>8. Auto Margin Examples</h3>
203
+
204
+ <h4>Single item pushed to end</h4>
205
+ <FlexRow
206
+ gap="10px"
207
+ style={{ background: "#fff3e0", padding: "10px" }}
208
+ >
209
+ <DemoBox>Item 1</DemoBox>
210
+ <DemoBox>Item 2</DemoBox>
211
+ <FlexItem alignX="end">
212
+ <DemoBox>Pushed to end</DemoBox>
213
+ </FlexItem>
214
+ </FlexRow>
215
+
216
+ <h4>Single item centered</h4>
217
+ <FlexRow
218
+ gap="10px"
219
+ style={{ background: "#e8f5e8", padding: "10px" }}
220
+ >
221
+ <DemoBox>Item 1</DemoBox>
222
+ <FlexItem alignX="center">
223
+ <DemoBox>Centered</DemoBox>
224
+ </FlexItem>
225
+ <DemoBox>Item 3</DemoBox>
226
+ </FlexRow>
227
+
228
+ <h4>Multiple items pushed to end</h4>
229
+ <FlexRow
230
+ gap="10px"
231
+ style={{ background: "#f0f4ff", padding: "10px" }}
232
+ >
233
+ <DemoBox>Item 1</DemoBox>
234
+ <FlexItem alignX="end">
235
+ <DemoBox>Pushed A</DemoBox>
236
+ </FlexItem>
237
+ <FlexItem alignX="end">
238
+ <DemoBox>Pushed B</DemoBox>
239
+ </FlexItem>
240
+ <FlexItem alignX="end">
241
+ <DemoBox>Pushed C</DemoBox>
242
+ </FlexItem>
243
+ </FlexRow>
244
+
245
+ <h4>Multiple items with different alignments</h4>
246
+ <FlexRow
247
+ gap="10px"
248
+ style={{ background: "#ffebee", padding: "10px" }}
249
+ >
250
+ <FlexItem alignX="start">
251
+ <DemoBox>Start</DemoBox>
252
+ </FlexItem>
253
+ <DemoBox>Normal</DemoBox>
254
+ <FlexItem alignX="center">
255
+ <DemoBox>Center</DemoBox>
256
+ </FlexItem>
257
+ <DemoBox>Normal</DemoBox>
258
+ <FlexItem alignX="end">
259
+ <DemoBox>End</DemoBox>
260
+ </FlexItem>
261
+ </FlexRow>
262
+ </section>
263
+
264
+ <section className="demo-section">
265
+ <h3>9. Combined Growth and Alignment</h3>
266
+
267
+ <FlexRow
268
+ gap="10px"
269
+ style={{ background: "#e1f5fe", padding: "10px" }}
270
+ >
271
+ <DemoBox>Fixed</DemoBox>
272
+ <FlexItem grow>
273
+ <DemoBox>Grows</DemoBox>
274
+ </FlexItem>
275
+ <FlexItem alignX="end">
276
+ <DemoBox>Pushed to end</DemoBox>
277
+ </FlexItem>
278
+ </FlexRow>
279
+ </section>
280
+
281
+ <section className="demo-section">
282
+ <h3>10. Real-World Example: Button with alignX</h3>
283
+
284
+ <FlexRow
285
+ gap="10px"
286
+ style={{ background: "#f5f5f5", padding: "10px" }}
287
+ >
288
+ <DemoBox>Content</DemoBox>
289
+ <DemoBox>More content</DemoBox>
290
+ <Button alignX="end">Action Button</Button>
291
+ </FlexRow>
292
+ </section>
293
+
294
+ <h2>FlexColumn</h2>
295
+
296
+ <section className="demo-section">
297
+ <h3>10. Basic FlexColumn</h3>
298
+ <FlexColumn style={{ height: "200px" }}>
299
+ <DemoBox>Item 1</DemoBox>
300
+ <DemoBox>Item 2</DemoBox>
301
+ <DemoBox>Item 3</DemoBox>
302
+ </FlexColumn>
303
+ </section>
304
+
305
+ <section className="demo-section">
306
+ <h3>11. FlexColumn with Gap</h3>
307
+ <FlexColumn gap="15px" style={{ height: "200px" }}>
308
+ <DemoBox>Item 1</DemoBox>
309
+ <DemoBox>Item 2</DemoBox>
310
+ <DemoBox>Item 3</DemoBox>
311
+ </FlexColumn>
312
+ </section>
313
+
314
+ <section className="demo-section">
315
+ <h3>12. FlexColumn with Horizontal Alignment</h3>
316
+
317
+ <h4>alignX="start"</h4>
318
+ <FlexColumn
319
+ alignX="start"
320
+ gap="10px"
321
+ style={{
322
+ height: "150px",
323
+ width: "300px",
324
+ background: "#fff3e0",
325
+ padding: "10px",
326
+ }}
327
+ >
328
+ <DemoBox>Short</DemoBox>
329
+ <DemoBox style={{ width: "200px" }}>Wide Item</DemoBox>
330
+ <DemoBox>Short</DemoBox>
331
+ </FlexColumn>
332
+
333
+ <h4>alignX="center" (default)</h4>
334
+ <FlexColumn
335
+ alignX="center"
336
+ gap="10px"
337
+ style={{
338
+ height: "150px",
339
+ width: "300px",
340
+ background: "#e8f5e8",
341
+ padding: "10px",
342
+ }}
343
+ >
344
+ <DemoBox>Short</DemoBox>
345
+ <DemoBox style={{ width: "200px" }}>Wide Item</DemoBox>
346
+ <DemoBox>Short</DemoBox>
347
+ </FlexColumn>
348
+
349
+ <h4>alignX="end"</h4>
350
+ <FlexColumn
351
+ alignX="end"
352
+ gap="10px"
353
+ style={{
354
+ height: "150px",
355
+ width: "300px",
356
+ background: "#ffebee",
357
+ padding: "10px",
358
+ }}
359
+ >
360
+ <DemoBox>Short</DemoBox>
361
+ <DemoBox style={{ width: "200px" }}>Wide Item</DemoBox>
362
+ <DemoBox>Short</DemoBox>
363
+ </FlexColumn>
364
+ </section>
365
+
366
+ <section className="demo-section">
367
+ <h3>13. FlexItem with alignX in Column</h3>
368
+
369
+ <FlexColumn
370
+ gap="10px"
371
+ style={{
372
+ height: "200px",
373
+ width: "300px",
374
+ background: "#f3e5f5",
375
+ padding: "10px",
376
+ }}
377
+ >
378
+ <FlexItem>
379
+ <DemoBox>Default</DemoBox>
380
+ </FlexItem>
381
+ <FlexItem alignX="start">
382
+ <DemoBox>alignX="start"</DemoBox>
383
+ </FlexItem>
384
+ <FlexItem alignX="center">
385
+ <DemoBox>alignX="center"</DemoBox>
386
+ </FlexItem>
387
+ <FlexItem alignX="end">
388
+ <DemoBox>alignX="end"</DemoBox>
389
+ </FlexItem>
390
+ </FlexColumn>
391
+ </section>
392
+
393
+ <section className="demo-section">
394
+ <h3>14. FlexItem with alignY in Column</h3>
395
+
396
+ <FlexColumn
397
+ gap="10px"
398
+ style={{
399
+ height: "300px",
400
+ width: "200px",
401
+ background: "#e1f5fe",
402
+ padding: "10px",
403
+ }}
404
+ >
405
+ <FlexItem>
406
+ <DemoBox>Default</DemoBox>
407
+ </FlexItem>
408
+ <FlexItem alignY="start">
409
+ <DemoBox>alignY="start"</DemoBox>
410
+ </FlexItem>
411
+ <FlexItem alignY="center">
412
+ <DemoBox>alignY="center"</DemoBox>
413
+ </FlexItem>
414
+ <FlexItem alignY="end">
415
+ <DemoBox>alignY="end"</DemoBox>
416
+ </FlexItem>
417
+ </FlexColumn>
418
+ </section>
419
+
420
+ <section className="demo-section">
421
+ <h3>15. Column Auto Margin Examples</h3>
422
+
423
+ <h4>Single item pushed to bottom</h4>
424
+ <FlexColumn
425
+ gap="10px"
426
+ style={{
427
+ height: "200px",
428
+ background: "#fff3e0",
429
+ padding: "10px",
430
+ }}
431
+ >
432
+ <DemoBox>Item 1</DemoBox>
433
+ <DemoBox>Item 2</DemoBox>
434
+ <FlexItem alignY="end">
435
+ <DemoBox>Pushed to bottom</DemoBox>
436
+ </FlexItem>
437
+ </FlexColumn>
438
+
439
+ <h4>Multiple items pushed to bottom</h4>
440
+ <FlexColumn
441
+ gap="10px"
442
+ style={{
443
+ height: "250px",
444
+ background: "#f0f4ff",
445
+ padding: "10px",
446
+ }}
447
+ >
448
+ <DemoBox>Item 1</DemoBox>
449
+ <FlexItem alignY="end">
450
+ <DemoBox>Pushed A</DemoBox>
451
+ </FlexItem>
452
+ <FlexItem alignY="end">
453
+ <DemoBox>Pushed B</DemoBox>
454
+ </FlexItem>
455
+ <FlexItem alignY="end">
456
+ <DemoBox>Pushed C</DemoBox>
457
+ </FlexItem>
458
+ </FlexColumn>
459
+
460
+ <h4>Item centered vertically</h4>
461
+ <FlexColumn
462
+ gap="10px"
463
+ style={{
464
+ height: "200px",
465
+ background: "#e8f5e8",
466
+ padding: "10px",
467
+ }}
468
+ >
469
+ <DemoBox>Item 1</DemoBox>
470
+ <FlexItem alignY="center">
471
+ <DemoBox>Centered</DemoBox>
472
+ </FlexItem>
473
+ <DemoBox>Item 3</DemoBox>
474
+ </FlexColumn>
475
+ </section>
476
+
477
+ <section className="demo-section">
478
+ <h3>16. Combined Growth and Alignment in Column</h3>
479
+
480
+ <FlexColumn
481
+ gap="10px"
482
+ style={{
483
+ height: "250px",
484
+ background: "#e1f5fe",
485
+ padding: "10px",
486
+ }}
487
+ >
488
+ <DemoBox>Fixed</DemoBox>
489
+ <FlexItem grow>
490
+ <DemoBox style="height: 100%; box-sizing: border-box;">
491
+ Grows
492
+ </DemoBox>
493
+ </FlexItem>
494
+ <FlexItem alignY="end">
495
+ <DemoBox>Pushed to bottom</DemoBox>
496
+ </FlexItem>
497
+ </FlexColumn>
498
+ </section>
499
+ </div>
500
+ );
501
+ };
502
+
503
+ render(<Demo />, document.getElementById("app"));
504
+ </script>
505
+ </body>
506
+ </html>
@@ -0,0 +1,154 @@
1
+ import { createContext } from "preact";
2
+ import { useContext } from "preact/hooks";
3
+
4
+ import { withPropsClassName } from "../props_composition/with_props_class_name.js";
5
+ import { withPropsStyle } from "../props_composition/with_props_style.js";
6
+ import { consumeSpacingProps } from "./spacing.jsx";
7
+
8
+ import.meta.css = /* css */ `
9
+ .navi_flex_row {
10
+ display: flex;
11
+ flex-direction: row;
12
+ align-items: center;
13
+ gap: 0;
14
+ }
15
+
16
+ .navi_flex_column {
17
+ display: flex;
18
+ flex-direction: column;
19
+ align-items: center;
20
+ gap: 0;
21
+ }
22
+
23
+ .navi_flex_item {
24
+ flex-shrink: 0;
25
+ }
26
+ `;
27
+
28
+ const FlexDirectionContext = createContext();
29
+
30
+ export const FlexRow = ({ alignY, gap, style, children, ...rest }) => {
31
+ const innerStyle = withPropsStyle(
32
+ {
33
+ alignItems: alignY,
34
+ gap,
35
+ ...consumeSpacingProps(rest),
36
+ },
37
+ style,
38
+ );
39
+
40
+ return (
41
+ <div {...rest} className="navi_flex_row" style={innerStyle}>
42
+ <FlexDirectionContext.Provider value="row">
43
+ {children}
44
+ </FlexDirectionContext.Provider>
45
+ </div>
46
+ );
47
+ };
48
+ export const FlexColumn = ({ alignX, gap, style, children, ...rest }) => {
49
+ const innerStyle = withPropsStyle(
50
+ {
51
+ alignItems: alignX,
52
+ gap,
53
+ ...consumeSpacingProps(rest),
54
+ },
55
+ style,
56
+ );
57
+
58
+ return (
59
+ <div {...rest} className="navi_flex_column" style={innerStyle}>
60
+ <FlexDirectionContext.Provider value="column">
61
+ {children}
62
+ </FlexDirectionContext.Provider>
63
+ </div>
64
+ );
65
+ };
66
+ export const useConsumAlignProps = (props) => {
67
+ const flexDirection = useContext(FlexDirectionContext);
68
+
69
+ const alignX = props.alignX;
70
+ const alignY = props.alignY;
71
+ delete props.alignX;
72
+ delete props.alignY;
73
+
74
+ const style = {};
75
+
76
+ if (flexDirection === "row") {
77
+ // In row direction: alignX controls justify-content, alignY controls align-self
78
+ // Default alignY is "center" from CSS, so only set alignSelf when different
79
+ if (alignY !== undefined && alignY !== "center") {
80
+ style.alignSelf = alignY;
81
+ }
82
+ // For row, alignX uses auto margins for positioning
83
+ // NOTE: Auto margins only work effectively for positioning individual items.
84
+ // When multiple adjacent items have the same auto margin alignment (e.g., alignX="end"),
85
+ // only the first item will be positioned as expected because subsequent items
86
+ // will be positioned relative to the previous item's margins, not the container edge.
87
+ if (alignX !== undefined) {
88
+ if (alignX === "start") {
89
+ style.marginRight = "auto";
90
+ } else if (alignX === "end") {
91
+ style.marginLeft = "auto";
92
+ } else if (alignX === "center") {
93
+ style.marginLeft = "auto";
94
+ style.marginRight = "auto";
95
+ }
96
+ }
97
+ } else if (flexDirection === "column") {
98
+ // In column direction: alignX controls align-self, alignY uses auto margins
99
+ // Default alignX is "center" from CSS, so only set alignSelf when different
100
+ if (alignX !== undefined && alignX !== "center") {
101
+ style.alignSelf = alignX;
102
+ }
103
+ // For column, alignY uses auto margins for positioning
104
+ // NOTE: Same auto margin limitation applies - multiple adjacent items with
105
+ // the same alignY won't all position relative to container edges.
106
+ if (alignY !== undefined) {
107
+ if (alignY === "start") {
108
+ style.marginBottom = "auto";
109
+ } else if (alignY === "end") {
110
+ style.marginTop = "auto";
111
+ } else if (alignY === "center") {
112
+ style.marginTop = "auto";
113
+ style.marginBottom = "auto";
114
+ }
115
+ }
116
+ }
117
+
118
+ return style;
119
+ };
120
+ export const FlexItem = ({
121
+ alignX,
122
+ alignY,
123
+ grow,
124
+ shrink,
125
+ className,
126
+ style,
127
+ children,
128
+ ...rest
129
+ }) => {
130
+ const flexDirection = useContext(FlexDirectionContext);
131
+ if (!flexDirection) {
132
+ console.warn(
133
+ "FlexItem must be used within a FlexRow or FlexColumn component.",
134
+ );
135
+ }
136
+
137
+ const innerClassName = withPropsClassName("navi_flex_item", className);
138
+ const alignStyle = useConsumAlignProps({ alignX, alignY });
139
+ const innerStyle = withPropsStyle(
140
+ {
141
+ flexGrow: grow ? 1 : undefined,
142
+ flexShrink: shrink ? 1 : undefined,
143
+ ...consumeSpacingProps(rest),
144
+ ...alignStyle,
145
+ },
146
+ style,
147
+ );
148
+
149
+ return (
150
+ <div {...rest} className={innerClassName} style={innerStyle}>
151
+ {children}
152
+ </div>
153
+ );
154
+ };