@invopop/popui 0.1.35 → 0.1.41

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.
Files changed (119) hide show
  1. package/dist/BaseButton.svelte +4 -0
  2. package/dist/BaseDropdown.svelte +42 -3
  3. package/dist/BaseDropdown.svelte.d.ts +1 -0
  4. package/dist/BaseTableHeaderOrderBy.svelte +35 -12
  5. package/dist/ButtonSearch.svelte +82 -0
  6. package/dist/ButtonSearch.svelte.d.ts +4 -0
  7. package/dist/ButtonUuidCopy.svelte +1 -0
  8. package/dist/DatePicker.svelte +96 -27
  9. package/dist/DatePicker.svelte.d.ts +5 -1
  10. package/dist/DrawerContext.svelte +443 -34
  11. package/dist/DrawerContextItem.svelte +36 -29
  12. package/dist/DropdownSelect.svelte +68 -18
  13. package/dist/DropdownSelect.svelte.d.ts +4 -1
  14. package/dist/DropdownSelectGroup.svelte +15 -0
  15. package/dist/DropdownSelectGroup.svelte.d.ts +7 -0
  16. package/dist/EmptyState.svelte +6 -2
  17. package/dist/InputSearch.svelte +45 -5
  18. package/dist/InputSelect.svelte +12 -3
  19. package/dist/InputText.svelte +25 -8
  20. package/dist/InputToggle.svelte +23 -6
  21. package/dist/StepIcon.svelte +35 -0
  22. package/dist/StepIcon.svelte.d.ts +4 -0
  23. package/dist/StepIconList.svelte +24 -31
  24. package/dist/TagStatus.svelte +1 -1
  25. package/dist/button/button.svelte +34 -3
  26. package/dist/button/button.svelte.d.ts +29 -0
  27. package/dist/clickOutside.d.ts +5 -2
  28. package/dist/clickOutside.js +9 -3
  29. package/dist/data-table/cells/boolean-cell.svelte +29 -0
  30. package/dist/data-table/cells/boolean-cell.svelte.d.ts +8 -0
  31. package/dist/data-table/cells/cell-skeleton.svelte +35 -0
  32. package/dist/data-table/cells/cell-skeleton.svelte.d.ts +4 -0
  33. package/dist/data-table/cells/currency-cell.svelte +10 -0
  34. package/dist/data-table/cells/currency-cell.svelte.d.ts +8 -0
  35. package/dist/data-table/cells/date-cell.svelte +10 -0
  36. package/dist/data-table/cells/date-cell.svelte.d.ts +8 -0
  37. package/dist/data-table/cells/tag-cell.svelte +12 -0
  38. package/dist/data-table/cells/tag-cell.svelte.d.ts +8 -0
  39. package/dist/data-table/cells/text-cell.svelte +10 -0
  40. package/dist/data-table/cells/text-cell.svelte.d.ts +8 -0
  41. package/dist/data-table/cells/uuid-cell.svelte +17 -0
  42. package/dist/data-table/cells/uuid-cell.svelte.d.ts +8 -0
  43. package/dist/data-table/column-definitions.d.ts +12 -0
  44. package/dist/data-table/column-definitions.js +42 -0
  45. package/dist/data-table/column-sizing-helpers.d.ts +6 -0
  46. package/dist/data-table/column-sizing-helpers.js +24 -0
  47. package/dist/data-table/create-columns.d.ts +3 -0
  48. package/dist/data-table/create-columns.js +67 -0
  49. package/dist/data-table/data-table-cell.svelte +94 -0
  50. package/dist/data-table/data-table-cell.svelte.d.ts +25 -0
  51. package/dist/data-table/data-table-header-cell.svelte +188 -0
  52. package/dist/data-table/data-table-header-cell.svelte.d.ts +25 -0
  53. package/dist/data-table/data-table-helpers.d.ts +10 -0
  54. package/dist/data-table/data-table-helpers.js +124 -0
  55. package/dist/data-table/data-table-pagination.svelte +221 -0
  56. package/dist/data-table/data-table-pagination.svelte.d.ts +4 -0
  57. package/dist/data-table/data-table-row.svelte +57 -0
  58. package/dist/data-table/data-table-row.svelte.d.ts +25 -0
  59. package/dist/data-table/data-table-svelte.svelte.d.ts +40 -0
  60. package/dist/data-table/data-table-svelte.svelte.js +115 -0
  61. package/dist/data-table/data-table-toolbar.svelte +19 -0
  62. package/dist/data-table/data-table-toolbar.svelte.d.ts +32 -0
  63. package/dist/data-table/data-table-types.d.ts +196 -0
  64. package/dist/data-table/data-table-types.js +1 -0
  65. package/dist/data-table/data-table-view-options.svelte +126 -0
  66. package/dist/data-table/data-table-view-options.svelte.d.ts +29 -0
  67. package/dist/data-table/data-table.svelte +437 -0
  68. package/dist/data-table/data-table.svelte.d.ts +25 -0
  69. package/dist/data-table/flex-render.svelte +40 -0
  70. package/dist/data-table/flex-render.svelte.d.ts +33 -0
  71. package/dist/data-table/index.d.ts +13 -0
  72. package/dist/data-table/index.js +13 -0
  73. package/dist/data-table/render-helpers.d.ts +90 -0
  74. package/dist/data-table/render-helpers.js +99 -0
  75. package/dist/data-table/table-setup.d.ts +39 -0
  76. package/dist/data-table/table-setup.js +151 -0
  77. package/dist/data-table/table-styles.d.ts +17 -0
  78. package/dist/data-table/table-styles.js +70 -0
  79. package/dist/drawer-dnd-helpers.d.ts +30 -0
  80. package/dist/drawer-dnd-helpers.js +72 -0
  81. package/dist/helpers.d.ts +1 -0
  82. package/dist/helpers.js +3 -0
  83. package/dist/index.d.ts +15 -3
  84. package/dist/index.js +28 -5
  85. package/dist/skeleton/index.d.ts +5 -0
  86. package/dist/skeleton/index.js +7 -0
  87. package/dist/skeleton/skeleton-avatar.svelte +14 -0
  88. package/dist/skeleton/skeleton-avatar.svelte.d.ts +7 -0
  89. package/dist/skeleton/skeleton-card.svelte +22 -0
  90. package/dist/skeleton/skeleton-card.svelte.d.ts +9 -0
  91. package/dist/skeleton/skeleton-list.svelte +25 -0
  92. package/dist/skeleton/skeleton-list.svelte.d.ts +8 -0
  93. package/dist/skeleton/skeleton.svelte +17 -0
  94. package/dist/skeleton/skeleton.svelte.d.ts +5 -0
  95. package/dist/svg/IconDelivery.svelte +1 -1
  96. package/dist/svg/IconOrder.svelte +1 -1
  97. package/dist/svg/IconPayment.svelte +1 -1
  98. package/dist/table/table-cell.svelte +4 -2
  99. package/dist/table/table-head.svelte +4 -2
  100. package/dist/table/table-header.svelte +1 -1
  101. package/dist/table/table-row.svelte +4 -2
  102. package/dist/table/table.svelte +2 -2
  103. package/dist/tailwind.theme.css +30 -6
  104. package/dist/tooltip/index.d.ts +2 -1
  105. package/dist/tooltip/index.js +3 -2
  106. package/dist/tooltip/tooltip-auto-hide.svelte +31 -0
  107. package/dist/tooltip/tooltip-auto-hide.svelte.d.ts +7 -0
  108. package/dist/types.d.ts +51 -73
  109. package/package.json +14 -8
  110. package/dist/BaseTable.svelte +0 -391
  111. package/dist/BaseTable.svelte.d.ts +0 -4
  112. package/dist/BaseTableCellContent.svelte +0 -58
  113. package/dist/BaseTableCellContent.svelte.d.ts +0 -4
  114. package/dist/BaseTableCheckbox.svelte +0 -33
  115. package/dist/BaseTableCheckbox.svelte.d.ts +0 -4
  116. package/dist/BaseTableHeaderContent.svelte +0 -67
  117. package/dist/BaseTableHeaderContent.svelte.d.ts +0 -4
  118. package/dist/BaseTableRow.svelte +0 -127
  119. package/dist/BaseTableRow.svelte.d.ts +0 -4
@@ -7,6 +7,7 @@
7
7
  import clsx from 'clsx'
8
8
  import TagStatus from './TagStatus.svelte'
9
9
  import { resolveIcon } from './helpers.js'
10
+ import { buttonVariants } from './button/button.svelte'
10
11
 
11
12
  let {
12
13
  value = $bindable(''),
@@ -17,7 +18,11 @@
17
18
  multiple = false,
18
19
  fullWidth = false,
19
20
  widthClass = 'min-w-[160px] max-w-[420px]',
20
- onSelect
21
+ onSelect,
22
+ onOpenChange,
23
+ stackLeft = false,
24
+ stackRight = false,
25
+ multipleLabel = 'items'
21
26
  }: DropdownSelectProps = $props()
22
27
 
23
28
  let selectDropdown: BaseDropdown | undefined = $state()
@@ -28,6 +33,10 @@
28
33
  resolveIcon(icon).then((res) => (resolvedIcon = res))
29
34
  })
30
35
 
36
+ $effect(() => {
37
+ onOpenChange?.(isOpen)
38
+ })
39
+
31
40
  let items = $derived(
32
41
  options.map((o) => ({
33
42
  ...o,
@@ -38,30 +47,45 @@
38
47
  )
39
48
 
40
49
  let selectedItems = $derived(items.filter((i) => i.selected))
41
- let selectedColor = $derived(!multiple && items.find((i) => i.selected)?.color)
42
- let selectedIcon = $derived(!multiple && items.find((i) => i.selected)?.icon)
50
+ let selectedItem = $derived(items.find((i) => i.selected))
51
+ let showSingleIcon = $derived(!multiple || selectedItems.length === 1)
52
+ let selectedColor = $derived(!multiple && selectedItem?.color)
53
+ let selectedColors = $derived(
54
+ multiple ? selectedItems.filter((i) => i.color).map((i) => i.color) : []
55
+ )
56
+ let hasMultipleColors = $derived(multiple && selectedColors.length > 0)
57
+ let selectedIcon = $derived(showSingleIcon && selectedItem?.icon)
43
58
  let selectedIconColor = $derived(
44
- (!multiple && items.find((i) => i.selected)?.iconClass) || 'text-foreground-default-secondary'
59
+ (showSingleIcon && selectedItem?.iconClass) || 'text-foreground-default-secondary'
45
60
  )
46
61
  let selectedLabel = $derived(
47
- `${selectedItems[0]?.label || ''}${selectedItems.length > 1 && multiple ? ' and more' : ''}` ||
48
- placeholder
62
+ hasMultipleColors && selectedItems.length > 1
63
+ ? `${selectedItems.length} ${multipleLabel}`
64
+ : `${selectedItems[0]?.label || ''}${selectedItems.length > 1 && multiple ? ' and more' : ''}` ||
65
+ placeholder
49
66
  )
50
67
 
68
+ let isStacked = $derived(stackLeft || stackRight)
69
+
51
70
  let styles = $derived(
52
- clsx('border backdrop-blur-sm backdrop-filter', {
53
- 'border-border-selected-bold shadow-active': isOpen,
54
- 'border-border-default-secondary hover:border-border-default-secondary-hover': !isOpen
55
- })
71
+ isStacked
72
+ ? buttonVariants({
73
+ variant: 'ghost',
74
+ stackedLeft: stackLeft,
75
+ stackedRight: stackRight
76
+ })
77
+ : clsx('border backdrop-blur-sm backdrop-filter dropdown-select', {
78
+ 'border-border-selected-bold shadow-active': isOpen,
79
+ 'border-border-default-secondary hover:border-border-default-secondary-hover': !isOpen
80
+ })
56
81
  )
57
82
 
58
83
  function handleClick(val: AnyProp) {
59
84
  value = val
60
85
 
61
- onSelect?.(value)
62
-
63
86
  if (multiple) return
64
87
 
88
+ onSelect?.(value)
65
89
  selectDropdown?.toggle()
66
90
  }
67
91
 
@@ -72,14 +96,27 @@
72
96
  if (isEqual(value, val)) return
73
97
 
74
98
  value = val
99
+ onSelect?.(value)
100
+ }
101
+
102
+ export const open = () => {
103
+ if (!isOpen) {
104
+ selectDropdown?.toggle()
105
+ }
106
+ }
107
+
108
+ export const toggle = () => {
109
+ selectDropdown?.toggle()
75
110
  }
76
111
  </script>
77
112
 
78
113
  {#snippet label()}
79
114
  <span
80
- class="flex-1 text-base truncate {selectedItems.length
81
- ? 'text-foreground'
82
- : 'text-foreground-default-secondary'}"
115
+ class={clsx('flex-1 text-base truncate', {
116
+ 'text-foreground': selectedItems.length,
117
+ 'text-foreground-default-secondary': !selectedItems.length,
118
+ 'font-normal': isStacked && !selectedItems.length
119
+ })}
83
120
  >
84
121
  {selectedLabel}
85
122
  </span>
@@ -90,13 +127,26 @@
90
127
  placement="bottom-start"
91
128
  {fullWidth}
92
129
  bind:this={selectDropdown}
93
- class={fullWidth ? '' : widthClass}
130
+ class={fullWidth || isStacked ? '' : widthClass}
94
131
  >
95
132
  {#snippet trigger()}
96
133
  <div
97
- class="{styles} dropdown-select flex items-center rounded-lg py-1.5 pl-2 pr-[28px] bg-background overflow-hidden w-full h-8"
134
+ class="{styles} flex items-center rounded-lg py-1.5 pl-2 bg-background overflow-hidden w-full h-7"
135
+ class:pr-[28px]={!isStacked}
136
+ class:pr-2={isStacked}
98
137
  >
99
- {#if selectedColor}
138
+ {#if hasMultipleColors}
139
+ <div class="flex items-center gap-1 flex-1 min-w-0">
140
+ <div class="flex items-center -space-x-0.5">
141
+ {#each selectedColors.slice(0, 3) as color}
142
+ <span class="border-l border-background rounded-xs flex first:border-l-0">
143
+ <TagStatus dot status={color} />
144
+ </span>
145
+ {/each}
146
+ </div>
147
+ {@render label()}
148
+ </div>
149
+ {:else if selectedColor}
100
150
  <div class="flex items-center gap-1 flex-1 min-w-0">
101
151
  <TagStatus dot status={selectedColor} />
102
152
  {@render label()}
@@ -1,4 +1,7 @@
1
1
  import type { DropdownSelectProps } from './types.ts';
2
- declare const DropdownSelect: import("svelte").Component<DropdownSelectProps, {}, "value">;
2
+ declare const DropdownSelect: import("svelte").Component<DropdownSelectProps, {
3
+ open: () => void;
4
+ toggle: () => void;
5
+ }, "value">;
3
6
  type DropdownSelect = ReturnType<typeof DropdownSelect>;
4
7
  export default DropdownSelect;
@@ -0,0 +1,15 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+
4
+ interface Props {
5
+ children: Snippet
6
+ }
7
+
8
+ let { children }: Props = $props()
9
+ </script>
10
+
11
+ <div
12
+ class="flex items-center h-7 rounded-md border border-border-default-secondary divide-x divide-border-default-secondary"
13
+ >
14
+ {@render children()}
15
+ </div>
@@ -0,0 +1,7 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ children: Snippet;
4
+ }
5
+ declare const DropdownSelectGroup: import("svelte").Component<Props, {}, "">;
6
+ type DropdownSelectGroup = ReturnType<typeof DropdownSelectGroup>;
7
+ export default DropdownSelectGroup;
@@ -31,8 +31,12 @@
31
31
  </div>
32
32
  {/if}
33
33
  <div class="flex flex-col items-center gap-0.5 text-center">
34
- <h4 class="font-medium text-foreground text-base">{title}</h4>
35
- <p class="text-foreground-default-secondary text-base">{description}</p>
34
+ {#if title}
35
+ <h4 class="font-medium text-foreground text-base">{title}</h4>
36
+ {/if}
37
+ {#if description}
38
+ <p class="text-foreground-default-secondary text-base">{description}</p>
39
+ {/if}
36
40
  </div>
37
41
  {#if children}
38
42
  <div class="mt-4">
@@ -1,9 +1,10 @@
1
1
  <script lang="ts">
2
2
  import { onMount } from 'svelte'
3
3
  import { Icon } from '@steeze-ui/svelte-icon'
4
- import { Search } from '@invopop/ui-icons'
4
+ import { Search, Close, Pulse } from '@invopop/ui-icons'
5
5
  import ShortcutWrapper from './ShortcutWrapper.svelte'
6
6
  import type { InputSearchProps } from './types.js'
7
+ import clsx from 'clsx'
7
8
 
8
9
  const debounce = (target: HTMLInputElement) => {
9
10
  clearTimeout(timer)
@@ -18,6 +19,8 @@
18
19
  placeholder = '',
19
20
  icon = Search,
20
21
  focusOnLoad = false,
22
+ size = 'md',
23
+ loading = false,
21
24
  oninput,
22
25
  onclick,
23
26
  onfocus,
@@ -42,6 +45,7 @@
42
45
 
43
46
  export const clear = () => {
44
47
  value = ''
48
+ oninput?.('')
45
49
  }
46
50
 
47
51
  let shortcutKeys = $derived(shortcut.split(''))
@@ -66,9 +70,22 @@
66
70
  <input
67
71
  bind:this={input}
68
72
  bind:value
69
- type="search"
70
- class="flex items-center gap-1 h-8 w-full px-2 py-1.5 pl-7 rounded-lg border bg-background-default-default text-base text-foreground-default-default placeholder:text-foreground-default-tertiary outline-none caret-foreground-accent focus:ring-0 border-border-default-secondary hover:border-border-default-secondary-hover focus:border-border-selected-bold focus:shadow-active"
71
- style:padding-right={shortcut ? `${shortcutKeys.length * 20 + 12}px` : undefined}
73
+ type="text"
74
+ class={clsx(
75
+ 'flex items-center gap-1 w-full px-2 pl-7 border bg-background-default-default text-base text-foreground-default-default placeholder:text-foreground-default-tertiary outline-none caret-foreground-accent focus:ring-0 border-border-default-secondary hover:border-border-default-secondary-hover focus:border-border-selected-bold focus:shadow-active',
76
+ {
77
+ 'h-[26px] rounded-sm pl-5': size === 'xs',
78
+ 'h-7 py-1 rounded-md pl-6': size === 'sm',
79
+ 'h-8 py-1.5 rounded-lg': size === 'md'
80
+ }
81
+ )}
82
+ style:padding-right={shortcut && (value || loading)
83
+ ? `${shortcutKeys.length * 20 + 32}px`
84
+ : shortcut
85
+ ? `${shortcutKeys.length * 20 + 12}px`
86
+ : value || loading
87
+ ? '28px'
88
+ : undefined}
72
89
  {placeholder}
73
90
  {...rest}
74
91
  oninput={handleInput}
@@ -76,7 +93,30 @@
76
93
  {onblur}
77
94
  {onclick}
78
95
  />
79
- <Icon src={icon} class="absolute left-2 size-4 text-foreground-default-tertiary" />
96
+ <Icon src={icon} class={clsx("absolute size-4 text-foreground-default-tertiary", {
97
+ 'left-1': size === 'xs',
98
+ 'left-1.5': size === 'sm',
99
+ 'left-2': size === 'md'
100
+ })} />
101
+
102
+ {#if loading}
103
+ <div
104
+ class="absolute"
105
+ style:right={shortcut ? `${shortcutKeys.length * 20 + 16}px` : '8px'}
106
+ >
107
+ <Icon src={Pulse} class="size-4 pulse-icon" />
108
+ </div>
109
+ {:else if value}
110
+ <button
111
+ type="button"
112
+ class="absolute text-foreground"
113
+ style:right={shortcut ? `${shortcutKeys.length * 20 + 16}px` : '8px'}
114
+ onclick={clear}
115
+ tabindex="-1"
116
+ >
117
+ <Icon src={Close} class="size-4" />
118
+ </button>
119
+ {/if}
80
120
 
81
121
  {#if shortcut}
82
122
  <div class="absolute right-2 flex items-center gap-1">
@@ -4,6 +4,7 @@
4
4
  import InputLabel from './InputLabel.svelte'
5
5
  import { resolveIcon } from './helpers.js'
6
6
  import InputError from './InputError.svelte'
7
+ import clsx from 'clsx'
7
8
 
8
9
  let {
9
10
  id = Math.random().toString(36).slice(2, 7),
@@ -17,6 +18,7 @@
17
18
  placeholder = 'Select one...',
18
19
  disablePlaceholder = true,
19
20
  errorText = '',
21
+ size = 'md',
20
22
  onchange,
21
23
  ...rest
22
24
  }: InputSelectProps = $props()
@@ -43,9 +45,16 @@
43
45
  {name}
44
46
  bind:value
45
47
  {disabled}
46
- class="h-8 w-full px-2 py-1.5 rounded-lg border bg-background text-base outline-none focus:ring-0 border-border hover:border-border-default-secondary-hover focus:border-border-selected-bold focus:shadow-active text-foreground ui-select"
47
- class:pl-7={resolvedIcon}
48
- class:text-foreground-default-secondary={!value}
48
+ class={clsx(
49
+ 'w-full px-2 rounded-lg border bg-background text-base outline-none focus:ring-0 border-border hover:border-border-default-secondary-hover focus:border-border-selected-bold focus:shadow-active text-foreground ui-select',
50
+ {
51
+ 'h-[26px]': size === 'xs',
52
+ 'h-7 py-1': size === 'sm',
53
+ 'h-8 py-1.5': size === 'md',
54
+ 'pl-7': resolvedIcon,
55
+ 'text-foreground-default-secondary': !value
56
+ }
57
+ )}
49
58
  {...rest}
50
59
  onchange={handleChange}
51
60
  >
@@ -13,6 +13,10 @@
13
13
  disabled = false,
14
14
  value = $bindable(''),
15
15
  focusOnLoad = false,
16
+ stackLeft = false,
17
+ stackRight = false,
18
+ widthClass = '',
19
+ size = 'md',
16
20
  oninput,
17
21
  onkeydown,
18
22
  onfocus,
@@ -30,21 +34,34 @@
30
34
  }, 750)
31
35
  }
32
36
 
37
+ let isStacked = $derived(stackLeft || stackRight)
38
+
33
39
  let inputStyles = $derived(
34
40
  clsx(
35
- 'h-8 w-full rounded-lg border px-2 py-1 text-base tracking-tight bg-background-default-default backdrop-blur-[2px] caret-foreground-accent',
41
+ 'px-2 py-1 text-base text-foreground tracking-tight bg-background-default-default backdrop-blur-[2px] caret-foreground-accent',
36
42
  'placeholder:text-foreground-default-tertiary',
37
43
  'outline-none focus:ring-0',
44
+ widthClass,
38
45
  {
46
+ // Width defaults
47
+ 'w-full': !isStacked && !widthClass,
48
+ // Size variants (applied to all)
49
+ 'h-[26px]': size === 'xs',
50
+ 'h-7': size === 'sm',
51
+ 'h-8': size === 'md',
52
+ // Stacked styles
53
+ 'border-0 rounded-none hover:bg-background-default-secondary focus:bg-background-default-default':
54
+ isStacked,
55
+ 'rounded-l-lg': isStacked && stackLeft && !stackRight,
56
+ 'rounded-r-lg': isStacked && stackRight && !stackLeft,
57
+ // Non-stacked styles
58
+ 'rounded-lg border': !isStacked,
39
59
  'pointer-events-none bg-background-default-secondary border-border-default-default':
40
- disabled
41
- },
42
- {
43
- 'text-foreground-critical border-border-critical-bold caret-foreground-critical': errorText
44
- },
45
- {
60
+ !isStacked && disabled,
61
+ 'text-foreground-critical border-border-critical-bold caret-foreground-critical':
62
+ !isStacked && errorText,
46
63
  'text-foreground border-border-default-secondary hover:border-border-default-secondary-hover focus:border-border-selected-bold focus:shadow-active':
47
- !errorText && !disabled
64
+ !isStacked && !errorText && !disabled
48
65
  }
49
66
  )
50
67
  )
@@ -2,46 +2,63 @@
2
2
  import clsx from 'clsx'
3
3
  import { createSwitch } from 'svelte-headlessui'
4
4
  import type { InputToggleProps } from './types'
5
+ import { cn } from './utils.js'
6
+ import { onMount } from 'svelte'
5
7
  const sw = createSwitch({ label: 'Set Preference' })
6
8
 
7
9
  let {
8
10
  id = Math.random().toString(36).slice(2, 7),
9
11
  label = '',
10
12
  checked = $bindable(false),
11
- onchange
13
+ onchange,
14
+ class: className = ''
12
15
  }: InputToggleProps = $props()
13
16
 
17
+ let hasInteracted = $state(false)
18
+
19
+ onMount(() => {
20
+ // Defer enabling transitions until after the next frame
21
+ requestAnimationFrame(() => {
22
+ requestAnimationFrame(() => {
23
+ hasInteracted = true
24
+ })
25
+ })
26
+ })
27
+
14
28
  $effect(() => {
15
29
  $sw.checked = checked
16
30
  })
17
31
 
18
32
  let toggleStyles = $derived(
19
33
  clsx(
20
- 'relative inline-flex h-5 w-[30px] shrink-0 cursor-pointer items-center rounded-md transition-colors duration-200 ease-in-out focus:outline-none focus:ring-0',
34
+ 'relative inline-flex h-5 w-[30px] shrink-0 items-center rounded-md focus:outline-none focus:ring-0',
21
35
  {
22
36
  'bg-background-default-tertiary': !$sw.checked,
23
- 'bg-background-accent': $sw.checked
37
+ 'bg-background-accent': $sw.checked,
38
+ 'transition-colors duration-200 ease-in-out': hasInteracted
24
39
  }
25
40
  )
26
41
  )
27
42
 
28
43
  let circleStyles = $derived(
29
44
  clsx(
30
- 'pointer-events-none inline-block size-4 transform rounded bg-icon-inverse-bold transition duration-200 ease-in-out',
45
+ 'pointer-events-none inline-block size-4 transform rounded bg-icon-inverse-bold',
31
46
  {
32
47
  'translate-x-[2px]': !$sw.checked,
33
- 'translate-x-[12px]': $sw.checked
48
+ 'translate-x-[12px]': $sw.checked,
49
+ 'transition duration-200 ease-in-out': hasInteracted
34
50
  }
35
51
  )
36
52
  )
37
53
 
38
54
  function handleChange() {
55
+ hasInteracted = true
39
56
  onchange?.($sw.checked)
40
57
  checked = $sw.checked
41
58
  }
42
59
  </script>
43
60
 
44
- <label for={id} class="inline-flex items-center gap-2 cursor-pointer">
61
+ <label for={id} class={cn("inline-flex items-center gap-2 cursor-pointer", className)}>
45
62
  <button {id} class={toggleStyles} use:sw.toggle onclick={handleChange}>
46
63
  <span class="sr-only">Use setting</span>
47
64
  <span aria-hidden="true" class={circleStyles}></span>
@@ -0,0 +1,35 @@
1
+ <script lang="ts">
2
+ import { TooltipAutoHide, TooltipContent, TooltipTrigger } from './tooltip'
3
+ import type { StepIconProps } from './types.js'
4
+
5
+ let {
6
+ name,
7
+ showMask = false,
8
+ tooltipContent,
9
+ children
10
+ }: StepIconProps = $props()
11
+ </script>
12
+
13
+ <TooltipAutoHide>
14
+ <TooltipTrigger class="shrink-0">
15
+ <div
16
+ style={showMask
17
+ ? `mask-image: url("data:image/svg+xml,%3Csvg width='26' height='28' viewBox='0 0 26 28' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M18.4004 0C21.7603 9.85818e-10 23.4402 0.000417824 24.7236 0.654297C25.1531 0.873098 25.552 1.14144 25.9131 1.45215C25.5579 1.90591 25.2449 2.39516 24.9805 2.91406C23.9996 4.83925 24 7.36007 24 12.4004V15.5996C24 20.6399 23.9996 23.1608 24.9805 25.0859C25.2447 25.6046 25.5581 26.0933 25.9131 26.5469C25.5519 26.8578 25.1532 27.1268 24.7236 27.3457C23.4402 27.9996 21.7603 28 18.4004 28H9.59961C6.23965 28 4.55977 27.9996 3.27637 27.3457C2.14739 26.7705 1.22954 25.8526 0.654297 24.7236C0.00041777 23.4402 9.8162e-10 21.7603 0 18.4004V9.59961C9.88222e-10 6.23965 0.000417662 4.55977 0.654297 3.27637C1.22954 2.14739 2.14739 1.22954 3.27637 0.654297C4.55977 0.000417608 6.23965 9.84018e-10 9.59961 0H18.4004Z' fill='white'/%3E%3C/svg%3E");
18
+ mask-size: 100% 100%;
19
+ mask-repeat: no-repeat;
20
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='26' height='28' viewBox='0 0 26 28' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M18.4004 0C21.7603 9.85818e-10 23.4402 0.000417824 24.7236 0.654297C25.1531 0.873098 25.552 1.14144 25.9131 1.45215C25.5579 1.90591 25.2449 2.39516 24.9805 2.91406C23.9996 4.83925 24 7.36007 24 12.4004V15.5996C24 20.6399 23.9996 23.1608 24.9805 25.0859C25.2447 25.6046 25.5581 26.0933 25.9131 26.5469C25.5519 26.8578 25.1532 27.1268 24.7236 27.3457C23.4402 27.9996 21.7603 28 18.4004 28H9.59961C6.23965 28 4.55977 27.9996 3.27637 27.3457C2.14739 26.7705 1.22954 25.8526 0.654297 24.7236C0.00041777 23.4402 9.8162e-10 21.7603 0 18.4004V9.59961C9.88222e-10 6.23965 0.000417662 4.55977 0.654297 3.27637C1.22954 2.14739 2.14739 1.22954 3.27637 0.654297C4.55977 0.000417608 6.23965 9.84018e-10 9.59961 0H18.4004Z' fill='white'/%3E%3C/svg%3E");
21
+ -webkit-mask-size: 100% 100%;
22
+ -webkit-mask-repeat: no-repeat;`
23
+ : ''}
24
+ >
25
+ {@render children()}
26
+ </div>
27
+ </TooltipTrigger>
28
+ <TooltipContent>
29
+ {#if tooltipContent}
30
+ {@render tooltipContent()}
31
+ {:else if name}
32
+ {name}
33
+ {/if}
34
+ </TooltipContent>
35
+ </TooltipAutoHide>
@@ -0,0 +1,4 @@
1
+ import type { StepIconProps } from './types.js';
2
+ declare const StepIcon: import("svelte").Component<StepIconProps, {}, "">;
3
+ type StepIcon = ReturnType<typeof StepIcon>;
4
+ export default StepIcon;
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
- import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from './tooltip'
2
+ import { TooltipProvider } from './tooltip'
3
+ import StepIcon from './StepIcon.svelte'
3
4
  import type { StepIconListProps } from './types.js'
4
5
 
5
6
  let { icons = [] }: StepIconListProps = $props()
@@ -9,42 +10,34 @@
9
10
  let restIcons = $derived(icons.slice(maxItems, icons.length))
10
11
  </script>
11
12
 
12
- {#snippet separator()}
13
- <div class="hidden sm:block h-px w-3 bg-border shrink-0"></div>
13
+ {#snippet restTooltip()}
14
+ {#each restIcons as restIcon}
15
+ <div>{restIcon.name}</div>
16
+ {/each}
14
17
  {/snippet}
15
18
 
16
19
  <TooltipProvider>
17
- <div class="flex flex-col space-y-2 sm:flex-row sm:space-y-0 items-center">
20
+ <div class="flex flex-col space-y-2 sm:flex-row sm:flex-nowrap sm:space-y-0 items-center">
18
21
  {#each mainIcons as icon, i (i)}
19
- <Tooltip>
20
- <TooltipTrigger class="shrink-0">
21
- <div
22
- class="p-1.5 rounded-md border border-border flex items-center space-x-1 bg-background text-icon"
23
- >
24
- <img src={icon.url} alt={icon.name} class="size-4" />
25
- </div>
26
- </TooltipTrigger>
27
- <TooltipContent>{icon.name}</TooltipContent>
28
- </Tooltip>
29
-
30
- {#if i < mainIcons.length - 1}
31
- {@render separator()}
32
- {/if}
22
+ <StepIcon name={icon.name} showMask={i < mainIcons.length - 1 || restIcons.length > 0}>
23
+ <div
24
+ class="p-1.5 rounded-md flex items-center space-x-1 bg-background-default-secondary text-icon shrink-0 {i >
25
+ 0
26
+ ? 'ml-[-2px]'
27
+ : ''}"
28
+ >
29
+ <img src={icon.url} alt={icon.name} class="size-4 shrink-0" />
30
+ </div>
31
+ </StepIcon>
33
32
  {/each}
34
33
  {#if restIcons.length}
35
- {@render separator()}
36
- <Tooltip>
37
- <TooltipTrigger class="shrink-0">
38
- <div class="flex items-center justify-center text-icon font-medium text-base size-7">
39
- +{restIcons.length}
40
- </div>
41
- </TooltipTrigger>
42
- <TooltipContent>
43
- {#each restIcons as restIcon}
44
- <div>{restIcon.name}</div>
45
- {/each}
46
- </TooltipContent>
47
- </Tooltip>
34
+ <StepIcon tooltipContent={restTooltip}>
35
+ <div
36
+ class="flex items-center justify-center text-icon font-medium text-sm size-7 shrink-0 rounded-md bg-background-default-secondary ml-[-1px]"
37
+ >
38
+ +{restIcons.length}
39
+ </div>
40
+ </StepIcon>
48
41
  {/if}
49
42
  </div>
50
43
  </TooltipProvider>
@@ -19,7 +19,7 @@
19
19
  'bg-background-status-void text-foreground-default-secondary': status === 'grey',
20
20
  'shadow-avatar text-foreground-default-secondary': status === 'empty',
21
21
  'px-1.5': dot,
22
- 'p-1!': dot && !label,
22
+ 'p-0!': dot && !label,
23
23
  'px-1': !dot
24
24
  })
25
25
  )
@@ -20,6 +20,8 @@
20
20
  'bg-transparent text-foreground hover:shadow-button-default active:shadow-button-pressed hover:bg-background-default-tertiary-hover active:bg-background-default-tertiary-hover [&_svg]:text-icon',
21
21
  secondary:
22
22
  'bg-background-default-tertiary text-foreground shadow-button-default hover:bg-background-default-tertiary-hover active:bg-background-default-tertiary-hover active:shadow-button-pressed [&_svg]:text-icon',
23
+ selected:
24
+ 'bg-background-selected border border-border-selected text-foreground-selected shadow-button-default hover:bg-background-selected-hover active:bg-background-selected-hover active:shadow-button-pressed [&_svg]:text-icon-selected',
23
25
  dark: 'bg-transparent text-foreground-inverse border border-border-inverse-secondary hover:bg-background-selected-inverse active:bg-background-inverse-tertiary active:shadow-button-dark-pressed [&_svg]:text-icon-inverse',
24
26
  'dark-ghost':
25
27
  'bg-transparent text-foreground-inverse hover:bg-background-selected-inverse active:bg-background-inverse-tertiary active:shadow-button-dark-pressed [&_svg]:text-icon-inverse'
@@ -36,6 +38,14 @@
36
38
  hasIcon: {
37
39
  true: '',
38
40
  false: ''
41
+ },
42
+ stackedLeft: {
43
+ true: '',
44
+ false: ''
45
+ },
46
+ stackedRight: {
47
+ true: '',
48
+ false: ''
39
49
  }
40
50
  },
41
51
  compoundVariants: [
@@ -98,6 +108,11 @@
98
108
  iconOnly: true,
99
109
  class: '[&_svg]:!text-icon-default-bold'
100
110
  },
111
+ {
112
+ variant: 'selected',
113
+ iconOnly: true,
114
+ class: '[&_svg]:!text-icon-selected'
115
+ },
101
116
  {
102
117
  variant: ['primary', 'warning', 'dark-ghost'],
103
118
  iconOnly: true,
@@ -122,13 +137,25 @@
122
137
  size: 'lg',
123
138
  iconOnly: true,
124
139
  class: 'w-8'
140
+ },
141
+ {
142
+ stackedLeft: true,
143
+ class:
144
+ '!rounded-l-none hover:!bg-background-default-secondary active:!bg-background-default-secondary'
145
+ },
146
+ {
147
+ stackedRight: true,
148
+ class:
149
+ '!rounded-r-none hover:!bg-background-default-secondary active:!bg-background-default-secondary'
125
150
  }
126
151
  ],
127
152
  defaultVariants: {
128
153
  variant: 'primary',
129
154
  size: 'md',
130
155
  iconOnly: false,
131
- hasIcon: false
156
+ hasIcon: false,
157
+ stackedLeft: false,
158
+ stackedRight: false
132
159
  }
133
160
  })
134
161
 
@@ -141,6 +168,8 @@
141
168
  icon?: IconSource
142
169
  iconPosition?: 'left' | 'right'
143
170
  iconClass?: string
171
+ stackedLeft?: boolean
172
+ stackedRight?: boolean
144
173
  }
145
174
  </script>
146
175
 
@@ -154,6 +183,8 @@
154
183
  icon,
155
184
  iconPosition = 'left',
156
185
  iconClass = '',
186
+ stackedLeft = false,
187
+ stackedRight = false,
157
188
  ref = $bindable(null),
158
189
  href = undefined,
159
190
  type = 'button',
@@ -220,7 +251,7 @@
220
251
  bind:this={ref}
221
252
  data-slot="button"
222
253
  class={cn(
223
- buttonVariants({ variant, size, iconOnly, hasIcon }),
254
+ buttonVariants({ variant, size, iconOnly, hasIcon, stackedLeft, stackedRight }),
224
255
  iconPosition === 'right' && 'flex-row-reverse',
225
256
  paddingClass,
226
257
  className
@@ -238,7 +269,7 @@
238
269
  bind:this={ref}
239
270
  data-slot="button"
240
271
  class={cn(
241
- buttonVariants({ variant, size, iconOnly, hasIcon }),
272
+ buttonVariants({ variant, size, iconOnly, hasIcon, stackedLeft, stackedRight }),
242
273
  'group',
243
274
  iconPosition === 'right' && 'flex-row-reverse',
244
275
  paddingClass,