@rokkit/ui 1.0.0-next.122 → 1.0.0-next.123

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/dist/index.d.ts CHANGED
@@ -38,3 +38,4 @@ export { default as ProgressDots } from "./ProgressDots.svelte";
38
38
  export { default as Card } from "./Card.svelte";
39
39
  export { default as Shine } from "./Shine.svelte";
40
40
  export { default as Tilt } from "./Tilt.svelte";
41
+ export { default as GraphPaper } from "./GraphPaper.svelte";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rokkit/ui",
3
- "version": "1.0.0-next.122",
3
+ "version": "1.0.0-next.123",
4
4
  "description": "Data driven UI components, improving DX",
5
5
  "author": "Jerry Thomas <me@jerrythomas.name>",
6
6
  "license": "MIT",
@@ -38,7 +38,7 @@
38
38
  "@rokkit/states": "latest",
39
39
  "d3-scale": "^4.0.2",
40
40
  "date-fns": "^4.1.0",
41
- "ramda": "^0.30.1",
42
- "typescript": "^5.8.3"
41
+ "ramda": "^0.31.3",
42
+ "typescript": "^5.9.2"
43
43
  }
44
44
  }
package/src/Card.svelte CHANGED
@@ -1,6 +1,6 @@
1
1
  <script>
2
2
  import { Proxy } from '@rokkit/states'
3
- let { class: classNames = '', value = $bindable(), fields, child, onClick } = $props()
3
+ let { class: classNames = '', value = $bindable(), fields, child, children, onClick } = $props()
4
4
 
5
5
  const proxy = $state(new Proxy(value, fields))
6
6
  const childSnippet = $derived(child ?? defaultChild)
@@ -36,6 +36,10 @@
36
36
  tabindex="-1"
37
37
  class={classNames}
38
38
  >
39
- {@render childSnippet(proxy)}
39
+ {#if children}
40
+ {@render children()}
41
+ {:else}
42
+ {@render childSnippet(proxy)}
43
+ {/if}
40
44
  </div>
41
45
  {/if}
@@ -0,0 +1,43 @@
1
+ <script>
2
+ let {
3
+ class: className = '',
4
+ unit = '.5rem',
5
+ majorGridSize = 5,
6
+ minorGridThickness = 0.5,
7
+ majorGridThickness = 0.5,
8
+ children
9
+ } = $props()
10
+ </script>
11
+
12
+ <div
13
+ data-graph-paper
14
+ class="flex flex-col {className}"
15
+ style:--unit={unit}
16
+ style:--size="calc( {majorGridSize} * {unit})"
17
+ style:--minor-grid="{minorGridThickness}px"
18
+ style:--major-grid="{majorGridThickness}px"
19
+ >
20
+ <content class="flex flex-col">
21
+ {@render children?.()}
22
+ </content>
23
+ </div>
24
+
25
+ <style>
26
+ [data-graph-paper] {
27
+ background-image:
28
+ linear-gradient(currentColor var(--major-grid), transparent var(--major-grid)),
29
+ linear-gradient(90deg, currentColor var(--major-grid), transparent var(--major-grid)),
30
+ linear-gradient(currentColor var(--minor-grid), transparent var(--minor-grid)),
31
+ linear-gradient(90deg, currentColor var(--minor-grid), transparent var(--minor-grid));
32
+ background-size:
33
+ var(--size) var(--size),
34
+ var(--size) var(--size),
35
+ var(--unit) var(--unit),
36
+ var(--unit) var(--unit);
37
+ background-position:
38
+ calc(-1 * var(--minor-grid)) calc(-1 * var(--minor-grid)),
39
+ calc(-1 * var(--minor-grid)) calc(-1 * var(--minor-grid)),
40
+ calc(-1 * var(--minor-grid)) calc(-1 * var(--minor-grid)),
41
+ calc(-1 * var(--minor-grid)) calc(-1 * var(--minor-grid));
42
+ }
43
+ </style>
@@ -0,0 +1,60 @@
1
+ <script>
2
+ // import { onMount } from 'svelte'
3
+ // import { $state, $derived } from 'svelte'
4
+
5
+ /**
6
+ * @typedef {Object} Props
7
+ * @property {any[]} options
8
+ * @property {any} value
9
+ */
10
+
11
+ /** @type Props */
12
+ let { options, value = $bindable() } = $props()
13
+
14
+ // const optionRefs = new Map<number, HTMLLabelElement>()
15
+
16
+ // const $indicatorStyle = $derived(() => {
17
+ // const el = optionRefs.get(value)
18
+ // if (!el) return ''
19
+ // const { offsetLeft, offsetWidth } = el
20
+ // return `transform: translateX(${offsetLeft}px); width: ${offsetWidth}px;`
21
+ // })
22
+
23
+ // onMount(() => {
24
+ // setTimeout(() => {
25
+ // optionRefs.get(value)?.offsetLeft
26
+ // })
27
+ // })
28
+ </script>
29
+
30
+ <div class="relative inline-flex rounded-md bg-gray-200 p-1" data-pickone-root role="radiogroup">
31
+ <!-- Indicator -->
32
+ <div
33
+ class="absolute h-full rounded-md bg-white shadow transition-all duration-300"
34
+ data-pickone-current
35
+ aria-hidden="true"
36
+ ></div>
37
+
38
+ {#each options as option, index (index)}
39
+ <div
40
+ class="relative z-10 cursor-pointer text-sm font-medium text-gray-700"
41
+ data-pickone-option
42
+ aria-checked={option === value}
43
+ role="radio"
44
+ >
45
+ <label class="bg-red flex size-full px-4 py-2">
46
+ <input
47
+ type="radio"
48
+ name="pickone"
49
+ value={option}
50
+ class="peer hidden"
51
+ checked={option === value}
52
+ onchange={() => (value = option)}
53
+ />
54
+ <span class="transition peer-checked:font-semibold peer-checked:text-black">
55
+ {option}
56
+ </span>
57
+ </label>
58
+ </div>
59
+ {/each}
60
+ </div>
@@ -24,7 +24,7 @@
24
24
  {#each steps as step, index (index)}
25
25
  <!-- svelte-ignore a11y-click-events-have-key-events -->
26
26
  <dot
27
- class="step flex h-3 w-3 rounded-full border-2 border-neutral-100 bg-neutral-300"
27
+ class="step border-surface-100 bg-surface-300 flex h-3 w-3 rounded-full border-2"
28
28
  on:click={handleClick}
29
29
  data-step={step}
30
30
  data-active={step === current}
@@ -45,7 +45,7 @@
45
45
  {#each data as { label }, stage (stage)}
46
46
  {#if label}
47
47
  <p
48
- class="col-span-3 flex w-full justify-center text-center font-medium leading-loose text-neutral-800"
48
+ class="text-surface-800 col-span-3 flex w-full justify-center text-center font-medium leading-loose"
49
49
  class:pending={stage > currentStage}
50
50
  >
51
51
  {label}
@@ -61,6 +61,6 @@
61
61
  grid-template-columns: repeat(var(--count), 2fr 6fr 2fr);
62
62
  }
63
63
  .pending {
64
- @apply font-light text-neutral-500;
64
+ @apply text-surface-500 font-light;
65
65
  }
66
66
  </style>
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * @typedef {Object} Props
5
5
  * @property {import('@rokkit/states').NodeProxy} value
6
- * @property {boolean} [expanded]
6
+ * @property {Boolean} [expanded]
7
7
  */
8
8
 
9
9
  /** @type {Props} */
package/src/Tabs.svelte CHANGED
@@ -18,7 +18,7 @@
18
18
  * @typedef {Object} TabProps
19
19
  * @property {string} [class] - Additional CSS class names
20
20
  * @property {string} [name] - Name for accessibility
21
- * @property {any[]} [items] - Array of tab items to display
21
+ * @property {any[]} [options] - Array of tab options to display
22
22
  * @property {FieldMapping} [fields] - Field mappings for extracting data
23
23
  * @property {'horizontal'|'vertical'} [orientation] - Orientation of the tab bar
24
24
  * @property {'before' | 'after' } [position] - Position of the tab bar
@@ -27,8 +27,8 @@
27
27
  * @property {number} [tabindex] - Tab index for keyboard navigation
28
28
  * @property {boolean} [editable] - Whether tabs can be added/removed
29
29
  * @property {string} [placeholder] - Placeholder text for input field
30
- * @property {import('svelte').Snippet} [child] - Snippet for rendering tab headers
31
- * @property {import('svelte').Snippet} [children] - Snippet for rendering tab content
30
+ * @property {import('svelte').Snippet} [tabItem] - Snippet for rendering tab headers
31
+ * @property {import('svelte').Snippet} [tabPanel] - Snippet for rendering tab content
32
32
  * @property {import('svelte').Snippet} [empty] - Snippet for rendering empty state
33
33
  * @property {Function} [onselect] - Callback when tab is selected
34
34
  * @property {Function} [onchange] - Callback when tab changes
@@ -41,7 +41,7 @@
41
41
  let {
42
42
  class: classes = '',
43
43
  name = 'tabs',
44
- items = $bindable([]),
44
+ options = $bindable([]),
45
45
  fields = {},
46
46
  value = $bindable(),
47
47
  orientation = 'horizontal',
@@ -49,8 +49,8 @@
49
49
  position = 'before',
50
50
  tabindex = 0,
51
51
  editable = false,
52
- child,
53
- children,
52
+ tabItem,
53
+ tabPanel,
54
54
  empty,
55
55
  placeholder = 'Select a tab to view its content.',
56
56
  icons,
@@ -59,15 +59,14 @@
59
59
  onmove,
60
60
  onadd,
61
61
  onremove,
62
- ...snippets
62
+ ...restProps
63
63
  } = $props()
64
64
 
65
65
  /** @type {Proxy[]} */
66
- let proxyItems = $derived(items.map((item) => new Proxy(item, fields)))
67
- let childSnippet = $derived(child ?? defaultChild)
68
- let childrenSnippet = $derived(children ?? defaultChildren)
66
+ let proxyItems = $derived(options.map((item) => new Proxy(item, fields)))
67
+ let tabItemSnippet = $derived(tabItem ?? defaultItem)
68
+ let tabPanelSnippet = $derived(tabPanel ?? defaultPanel)
69
69
  let emptyMessage = $derived(empty ?? defaultEmpty)
70
- let activeItem = $derived(proxyItems.find((proxy) => equals(proxy.value, value)))
71
70
 
72
71
  function handleAction(event) {
73
72
  const { name, data } = event.detail
@@ -87,18 +86,17 @@
87
86
  }
88
87
  let tabIcons = $derived({ ...pick(['add', 'close'], defaultStateIcons.action), ...icons })
89
88
  let emitter = createEmitter({ onchange, onmove, onselect }, ['select', 'change', 'move'])
90
- let wrapper = new ListController(items, value, fields)
91
- $effect(() => {
92
- wrapper.update(items)
93
- })
89
+ let wrapper = new ListController(options, value, fields)
90
+
91
+ $effect(() => wrapper.update(options))
94
92
  </script>
95
93
 
96
- {#snippet defaultChild(item)}
94
+ {#snippet defaultItem(item)}
97
95
  {item.get('text') || item.get('label') || item.get('name')}
98
96
  {/snippet}
99
97
 
100
- {#snippet defaultChildren(item)}
101
- <div data-tab-content-default>
98
+ {#snippet defaultPanel(item)}
99
+ <div data-tabs-content>
102
100
  {item.get('content')}
103
101
  </div>
104
102
  {/snippet}
@@ -108,6 +106,7 @@
108
106
  {/snippet}
109
107
 
110
108
  <div
109
+ {...restProps}
111
110
  data-tabs-root
112
111
  data-orientation={orientation}
113
112
  data-position={position}
@@ -119,12 +118,21 @@
119
118
  {tabindex}
120
119
  onaction={handleAction}
121
120
  >
121
+ {#if proxyItems.length === 0}
122
+ <div data-tabs-empty>
123
+ {@render emptyMessage()}
124
+ </div>
125
+ {:else if wrapper.focusedKey === null && value === null}
126
+ <div data-tabs-placeholder>
127
+ {placeholder}
128
+ </div>
129
+ {/if}
122
130
  <div data-tabs-list>
123
131
  {#each proxyItems as item, index (index)}
124
132
  {@const key = getKeyFromPath([index])}
125
133
  {@const isSelected = equals(item.value, value)}
126
134
  {@const isFocused = wrapper.focusedKey === key}
127
- <div
135
+ <button
128
136
  data-tabs-trigger
129
137
  data-path={getKeyFromPath([index])}
130
138
  role="tab"
@@ -132,8 +140,10 @@
132
140
  aria-controls="tab-panel-{index}"
133
141
  class:selected={isSelected}
134
142
  class:focused={isFocused}
143
+ tabindex="0"
144
+ id={`tab-${index}`}
135
145
  >
136
- {@render childSnippet(item)}
146
+ {@render tabItemSnippet(item)}
137
147
  {#if editable}
138
148
  <Icon
139
149
  data-icon-remove
@@ -142,25 +152,25 @@
142
152
  onclick={() => handleRemove(item.value)}
143
153
  />
144
154
  {/if}
145
- </div>
155
+ </button>
146
156
  {/each}
147
157
  {#if editable}
148
158
  <Icon data-icon-add name={tabIcons.add} role="button" onclick={handleAdd} />
149
159
  {/if}
150
160
  </div>
151
161
 
152
- <!-- Tab Content -->
153
- <div data-tabs-content role="tabpanel">
154
- {#if proxyItems.length === 0}
155
- <div data-empty>
156
- {@render emptyMessage()}
157
- </div>
158
- {:else if activeItem}
159
- {@render childrenSnippet(activeItem)}
160
- {:else}
161
- <div data-placeholder>
162
- {placeholder}
163
- </div>
164
- {/if}
165
- </div>
162
+ <!-- Tab Panels -->
163
+ {#each proxyItems as item, index (index)}
164
+ {@const isVisible = equals(item.value, value)}
165
+
166
+ <div
167
+ data-tabs-panel
168
+ role="tabpanel"
169
+ id="tab-panel-{index}"
170
+ aria-labelledby="tab-{index}"
171
+ data-panel-active={isVisible}
172
+ >
173
+ {@render tabPanelSnippet(item)}
174
+ </div>
175
+ {/each}
166
176
  </div>
package/src/index.js CHANGED
@@ -45,3 +45,4 @@ export { default as ProgressDots } from './ProgressDots.svelte'
45
45
  export { default as Card } from './Card.svelte'
46
46
  export { default as Shine } from './Shine.svelte'
47
47
  export { default as Tilt } from './Tilt.svelte'
48
+ export { default as GraphPaper } from './GraphPaper.svelte'
@@ -1,96 +0,0 @@
1
- <script>
2
- import { defaultFields, defaultStateIcons, noop, getSnippet, FieldMapper } from '@rokkit/core'
3
- import { ListController } from '@rokkit/states'
4
- import { navigator } from '@rokkit/actions'
5
- import Icon from './Icon.svelte'
6
- import Item from './Item.svelte'
7
-
8
- /**
9
- * @typedef {Object} Props
10
- * @property {string} [class]
11
- * @property {any} [options]
12
- * @property {import('@rokkit/core').FieldMapping} [fields]
13
- * @property {any} [value]
14
- * @property {boolean} [below]
15
- * @property {string} [align]
16
- * @property {boolean} [editable]
17
- * @property {any} [icons]
18
- */
19
-
20
- /** @type {Props} */
21
- let {
22
- class: className = '',
23
- options = $bindable([]),
24
- value = $bindable(null),
25
- icons = $bindable(defaultStateIcons.action),
26
- fields = defaultFields,
27
- below = false,
28
- align = 'left',
29
- editable = false,
30
- onremove = noop,
31
- onadd = noop,
32
- onselect = noop,
33
- stub,
34
- ...extra
35
- } = $props()
36
-
37
- function handleRemove(event) {
38
- if (typeof event.detail === Object) {
39
- event.detail[fields.isDeleted] = true
40
- } else {
41
- options = options.filter((i) => i !== event.detail)
42
- }
43
-
44
- onremove({ item: event.detail })
45
- }
46
- function handleAdd(event) {
47
- event.stopPropagation()
48
- onadd()
49
- }
50
- function handleNav(event) {
51
- value = event.detail.node
52
- cursor = event.detail.path
53
-
54
- onselect({ item: value, indices: cursor })
55
- }
56
- let stateIcons = $derived({ ...defaultStateIcons.action, ...icons })
57
- let filtered = $derived(options.filter((item) => !item[fields.deleted]))
58
- let wrapper = $derived(new ListController(options, value, fields))
59
- let mapper = new FieldMapper(fields)
60
- </script>
61
-
62
- <rk-tabs
63
- class="flex w-full {className}"
64
- class:is-below={below}
65
- class:justify-center={align === 'center'}
66
- class:justify-end={align === 'right'}
67
- tabindex="0"
68
- role="listbox"
69
- use:navigator={{ wrapper, horizontal: true }}
70
- onaction={handleNav}
71
- onremove={handleRemove}
72
- onadd={handleAdd}
73
- >
74
- {#each filtered as item, index (index)}
75
- {@const Template = getSnippet(extra, mapper.get('snippet', item), stub)}
76
- <rk-tab>
77
- {#if Template}
78
- <Template value={item} {fields} />
79
- {:else}
80
- <Item value={item} {fields} />
81
- {/if}
82
- {#if editable}
83
- <Icon
84
- name="remove"
85
- role="button"
86
- label="Delete Tab"
87
- size="small"
88
- onclick={() => handleRemove(item)}
89
- />
90
- {/if}
91
- </rk-tab>
92
- {/each}
93
- {#if editable}
94
- <Icon name="add" role="button" label="Add Tab" size="small" onclick={handleAdd} />
95
- {/if}
96
- </rk-tabs>