fvn-ui 0.1.0-alpha.13 → 0.1.0-alpha.15

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/AGENTS.md CHANGED
@@ -127,6 +127,21 @@ ui.col({ center: true }, [
127
127
  | `center: true` | Center content (justify for col, align for row) |
128
128
  | `distribute: 'equal'` | Children share space equally |
129
129
 
130
+ ## Custom Icons
131
+
132
+ Extend with icons from [Lucide](https://lucide.dev/icons) or [Feather](https://feathericons.com):
133
+
134
+ ```js
135
+ // Add icons using SVG inner content (no <svg> wrapper)
136
+ ui.svg.extend({
137
+ github: '<path d="M15 22v-4a4.8..."/>',
138
+ custom: '<circle cx="12" cy="12" r="10"/>'
139
+ })
140
+
141
+ ui.button({ icon: 'github' }) // Now works
142
+ ui.svg.list() // Get all icon names
143
+ ```
144
+
130
145
  ## Full Documentation
131
146
 
132
147
  For complete API reference, examples, and patterns, read:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fvn-ui",
3
- "version": "0.1.0-alpha.13",
3
+ "version": "0.1.0-alpha.15",
4
4
  "description": "Minimalist vanilla JS component library",
5
5
  "type": "module",
6
6
  "sideEffects": [
package/src/fvn-ui/LLM.md CHANGED
@@ -475,10 +475,42 @@ avatar({ src: 'photo.jpg', name: 'Jane', size: 'large' })
475
475
 
476
476
  ---
477
477
 
478
- ## Available Icons
478
+ ## Icons
479
+
480
+ ### Built-in Icons
479
481
 
480
482
  `check`, `x`, `plus`, `minus`, `search`, `settings`, `user`, `users`, `mail`, `phone`, `calendar`, `clock`, `star`, `heart`, `home`, `menu`, `more`, `edit`, `trash`, `copy`, `download`, `upload`, `link`, `external`, `chevron-up`, `chevron-down`, `chevron-left`, `chevron-right`, `arrow-up`, `arrow-down`, `arrow-left`, `arrow-right`, `sun`, `moon`, `eye`, `eye-off`, `lock`, `unlock`, `bell`, `filter`, `sort`, `refresh`, `info`, `warning`, `error`, `success`
481
483
 
484
+ ### Extending Icons
485
+
486
+ Add custom icons from [Feather Icons](https://feathericons.com) or [Lucide](https://lucide.dev/icons):
487
+
488
+ ```js
489
+ import { svg } from 'fvn-ui'
490
+
491
+ // Add custom icons - use SVG inner content only (no <svg> wrapper)
492
+ svg.extend({
493
+ // From Lucide: copy the path/circle/line elements inside <svg>
494
+ github: '<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/><path d="M9 18c-4.51 2-5-2-7-2"/>',
495
+
496
+ // From Feather: same approach
497
+ activity: '<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>'
498
+ })
499
+
500
+ // Now use them
501
+ button({ icon: 'github', label: 'GitHub' })
502
+ svg('activity')
503
+
504
+ // List all available icons
505
+ svg.list() // ['check', 'x', ..., 'github', 'activity']
506
+ ```
507
+
508
+ **How to extract icon content:**
509
+ 1. Find icon on [lucide.dev/icons](https://lucide.dev/icons) or [feathericons.com](https://feathericons.com)
510
+ 2. Copy the SVG code
511
+ 3. Remove the outer `<svg>...</svg>` wrapper
512
+ 4. Keep only the inner elements (`<path>`, `<circle>`, `<line>`, `<polyline>`, etc.)
513
+
482
514
  ---
483
515
 
484
516
  ## Colors
@@ -34,6 +34,7 @@ export function button(...args) {
34
34
  size,
35
35
  color,
36
36
  muted,
37
+ loading,
37
38
  type = 'button',
38
39
  disabled,
39
40
  attrs = {},
@@ -73,6 +74,7 @@ export function button(...args) {
73
74
  shape && !isMinimal && bem(shape),
74
75
  size && bem.core('size', size),
75
76
  muted && bem('muted'),
77
+ loading && 'loading',
76
78
  configToClasses(props),
77
79
  rest.class
78
80
  ],
@@ -100,7 +102,7 @@ export function button(...args) {
100
102
  }
101
103
  },
102
104
  toggleLoading(text) {
103
- this.classList.toggle('loading', !!text);
105
+ this.classList.toggle('loading', typeof text === 'string');
104
106
  this.textContent = text || label;
105
107
  }
106
108
  });
@@ -34,7 +34,7 @@ export function collapsible(...args) {
34
34
  let state = !!open;
35
35
  let iconEl;
36
36
 
37
- const getIcon = () => svg(state ? 'chevronDown' : 'chevronRight');
37
+ const getIcon = () => svg(state ? 'chevron-down' : 'chevron-right');
38
38
 
39
39
  const toggle = () => {
40
40
  if (disabled) {
@@ -55,7 +55,7 @@ export function collapsible(...args) {
55
55
  class: bem.el('trigger'),
56
56
  variant: 'none',
57
57
  disabled,
58
- icon: state ? 'chevronDown' : 'chevronRight',
58
+ icon: state ? 'chevron-down' : 'chevron-right',
59
59
  label,
60
60
  onclick: toggle,
61
61
  ref: (btn) => iconEl = btn.querySelector('.ui-btn__icon')
@@ -29,7 +29,7 @@ export function draggable(...args) {
29
29
  parent,
30
30
  id,
31
31
  items: initialItems = [],
32
- icon = 'menu',
32
+ icon = 'grip',
33
33
  border = true,
34
34
  handlePosition = 'right',
35
35
  index = true,
@@ -273,7 +273,7 @@ export function selectComponent(...args) {
273
273
  children: [
274
274
  el('span', { class: bem.el('value'), ref: (e) => valueEl = e }),
275
275
  el('span', { class: bem.el('actions'), children: [
276
- button({ icon: 'chevronDown', variant: 'stripped', muted: true }),
276
+ button({ icon: 'chevron-down', variant: 'stripped', muted: true }),
277
277
  multiselect && el('span', {
278
278
  class: bem.el('badge'),
279
279
  ref: (e) => badgeEl = e,
@@ -5,18 +5,18 @@ import './svg.css';
5
5
 
6
6
  const shapes = {
7
7
  check: '<polyline points="20 6 9 17 4 12"></polyline>',
8
- chevronDown: '<polyline points="6 9 12 15 18 9"></polyline>',
9
- chevronUp: '<polyline points="18 15 12 9 6 15"></polyline>',
10
- chevronLeft: '<polyline points="15 18 9 12 15 6"></polyline>',
11
- chevronRight: '<polyline points="9 18 15 12 9 6"></polyline>',
12
- arrowRight: '<line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline>',
13
- arrowLeft: '<line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline>',
14
- arrowUp: '<line x1="12" y1="19" x2="12" y2="5"></line><polyline points="5 12 12 5 19 12"></polyline>',
15
- arrowDown: '<line x1="12" y1="5" x2="12" y2="19"></line><polyline points="19 12 12 19 5 12"></polyline>',
8
+ 'chevron-down': '<polyline points="6 9 12 15 18 9"></polyline>',
9
+ 'chevron-up': '<polyline points="18 15 12 9 6 15"></polyline>',
10
+ 'chevron-left': '<polyline points="15 18 9 12 15 6"></polyline>',
11
+ 'chevron-right': '<polyline points="9 18 15 12 9 6"></polyline>',
12
+ 'arrow-right': '<line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline>',
13
+ 'arrow-left': '<line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline>',
14
+ 'arrow-up': '<line x1="12" y1="19" x2="12" y2="5"></line><polyline points="5 12 12 5 19 12"></polyline>',
15
+ 'arrow-down': '<line x1="12" y1="5" x2="12" y2="19"></line><polyline points="19 12 12 19 5 12"></polyline>',
16
16
  settings: '<path d="M10 5H3"/><path d="M12 19H3"/><path d="M14 3v4"/><path d="M16 17v4"/><path d="M21 12h-9"/><path d="M21 19h-5"/><path d="M21 5h-7"/><path d="M8 10v4"/><path d="M8 12H3"/>',
17
17
  x: '<line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line>',
18
18
  dots: '<circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="19" r="1"></circle>',
19
- dotsHorizontal: '<circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle>',
19
+ 'dots-horizontal': '<circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle>',
20
20
  menu: '<line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line>',
21
21
  code: '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="m10 8 4 4-4 4"/>',
22
22
  enter: '<path d="M11 9a1 1 0 0 0 1-1V5.061a1 1 0 0 1 1.811-.75l6.836 6.836a1.207 1.207 0 0 1 0 1.707l-6.836 6.835a1 1 0 0 1-1.811-.75V16a1 1 0 0 0-1-1H9a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1z"/><path d="M4 9v6"/>',
@@ -24,7 +24,6 @@ const shapes = {
24
24
  chat: '<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>',
25
25
  moon: '<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>',
26
26
  sun: '<circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>',
27
- hexagon: '<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>',
28
27
  circle: '<circle cx="12" cy="12" r="10"></circle>',
29
28
  plus: '<line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line>',
30
29
  minus: '<line x1="5" y1="12" x2="19" y2="12"></line>',
@@ -41,17 +40,17 @@ const shapes = {
41
40
  star: '<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>',
42
41
  bell: '<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path><path d="M13.73 21a2 2 0 0 1-3.46 0"></path>',
43
42
  info: '<circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line>',
44
- alertCircle: '<circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line>',
45
- alertTriangle: '<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line>',
46
- checkCircle: '<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline>',
47
- xCircle: '<circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line>',
43
+ 'alert-circle': '<circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line>',
44
+ 'alert-triangle': '<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line>',
45
+ 'check-circle': '<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline>',
46
+ 'x-circle': '<circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line>',
48
47
  eye: '<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle>',
49
- eyeOff: '<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path><line x1="1" y1="1" x2="23" y2="23"></line>',
48
+ 'eye-off': '<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path><line x1="1" y1="1" x2="23" y2="23"></line>',
50
49
  copy: '<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>',
51
50
  download: '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line>',
52
51
  upload: '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line>',
53
52
  link: '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>',
54
- externalLink: '<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line>',
53
+ 'external-link': '<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line>',
55
54
  filter: '<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon>',
56
55
  sliders: '<line x1="4" y1="21" x2="4" y2="14"></line><line x1="4" y1="10" x2="4" y2="3"></line><line x1="12" y1="21" x2="12" y2="12"></line><line x1="12" y1="8" x2="12" y2="3"></line><line x1="20" y1="21" x2="20" y2="16"></line><line x1="20" y1="12" x2="20" y2="3"></line><line x1="1" y1="14" x2="7" y2="14"></line><line x1="9" y1="8" x2="15" y2="8"></line><line x1="17" y1="16" x2="23" y2="16"></line>',
57
56
  refresh: '<polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>',
@@ -61,6 +60,7 @@ const shapes = {
61
60
  unlock: '<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 9.9-1"></path>',
62
61
  save: '<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline>',
63
62
  file: '<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline>',
63
+ 'file-text': '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline>',
64
64
  folder: '<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>',
65
65
  image: '<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline>',
66
66
  grid: '<rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect>',
@@ -72,7 +72,10 @@ const shapes = {
72
72
  pause: '<rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect>',
73
73
  rabbit: '<path d="M13 16a3 3 0 0 1 2.24 5"/><path d="M18 12h.01"/><path d="M18 21h-8a4 4 0 0 1-4-4 7 7 0 0 1 7-7h.2L9.6 6.4a1 1 0 1 1 2.8-2.8L15.8 7h.2c3.3 0 6 2.7 6 6v1a2 2 0 0 1-2 2h-1a3 3 0 0 0-3 3"/><path d="M20 8.54V4a2 2 0 1 0-4 0v3"/><path d="M7.612 12.524a3 3 0 1 0-1.6 4.3"/>',
74
74
  bird: '<path d="M16 7h.01"/><path d="M3.4 18H12a8 8 0 0 0 8-8V7a4 4 0 0 0-7.28-2.3L2 20"/><path d="m20 7 2 .5-2 .5"/><path d="M10 18v3"/><path d="M14 17.75V21"/><path d="M7 18a6 6 0 0 0 3.84-10.61"/>',
75
- toggle: '<rect x="1" y="5" width="22" height="14" rx="7" ry="7"></rect><circle cx="8" cy="12" r="3"></circle>'
75
+ toggle: '<rect x="1" y="5" width="22" height="14" rx="7" ry="7"></rect><circle cx="8" cy="12" r="3"></circle>',
76
+ hash: '<line x1="4" y1="9" x2="20" y2="9"></line><line x1="4" y1="15" x2="20" y2="15"></line><line x1="10" y1="3" x2="8" y2="21"></line><line x1="16" y1="3" x2="14" y2="21"></line>',
77
+ type: '<polyline points="4 7 4 4 20 4 20 7"></polyline><line x1="9" y1="20" x2="15" y2="20"></line><line x1="12" y1="4" x2="12" y2="20"></line>',
78
+ grip: '<circle cx="12" cy="5" r="1"/><circle cx="19" cy="5" r="1"/><circle cx="5" cy="5" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/><circle cx="12" cy="19" r="1"/><circle cx="19" cy="19" r="1"/><circle cx="5" cy="19" r="1"/>'
76
79
  };
77
80
 
78
81
  const wrap = (shape, n) => !shape
@@ -83,4 +86,28 @@ const wrap = (shape, n) => !shape
83
86
  </svg>
84
87
  `;
85
88
 
86
- export const svg = n => wrap(shapes[n], n);
89
+ /**
90
+ * Get SVG icon markup by name
91
+ * @param {string} n - Icon name
92
+ * @returns {string} SVG markup
93
+ * @property {Function} extend - Add custom icons: `svg.extend({ iconName: '<path.../>' })`
94
+ * @property {Function} list - Get available icon names: `svg.list()` → string[]
95
+ * @example
96
+ * // Get built-in icon
97
+ * svg('check')
98
+ * svg('settings')
99
+ *
100
+ * // Extend with custom icons
101
+ * svg.extend({
102
+ * myIcon: '<path d="M12 2L2 22h20L12 2z"/>',
103
+ * logo: '<circle cx="12" cy="12" r="10"/>'
104
+ * })
105
+ * svg('myIcon') // Now available
106
+ *
107
+ * // List all icons
108
+ * svg.list() // ['check', 'x', 'plus', ..., 'myIcon', 'logo']
109
+ */
110
+ export const svg = n => wrap(shapes[n], n);
111
+
112
+ svg.extend = (icons) => Object.assign(shapes, icons);
113
+ svg.list = () => Object.keys(shapes);
@@ -1,5 +1,6 @@
1
1
  /*
2
2
  TODO
3
+ - move "dashboard" component under layout category?
3
4
  - Add clamping functionality to number input component? custom arrows
4
5
  - "copy text" icon option for input/textarea
5
6
  - keyboard element ([enter]) to display where available (eg. input)