@invopop/popui 0.1.4-beta.4 → 0.1.4-beta.40

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 (44) hide show
  1. package/dist/BaseDropdown.svelte +16 -4
  2. package/dist/BaseDropdown.svelte.d.ts +1 -0
  3. package/dist/BaseTableHeaderContent.svelte +1 -1
  4. package/dist/BaseTableHeaderOrderBy.svelte +28 -16
  5. package/dist/ButtonSearch.svelte +81 -0
  6. package/dist/ButtonSearch.svelte.d.ts +4 -0
  7. package/dist/DatePicker.svelte +82 -26
  8. package/dist/DrawerContext.svelte +231 -109
  9. package/dist/DrawerContextItem.svelte +6 -2
  10. package/dist/DropdownSelect.svelte +10 -6
  11. package/dist/EmptyState.svelte +6 -2
  12. package/dist/InputSearch.svelte +45 -5
  13. package/dist/InputSelect.svelte +12 -3
  14. package/dist/InputText.svelte +25 -8
  15. package/dist/InputToggle.svelte +23 -6
  16. package/dist/StepIcon.svelte +50 -0
  17. package/dist/StepIcon.svelte.d.ts +4 -0
  18. package/dist/StepIconList.svelte +24 -31
  19. package/dist/data-table/cells/boolean-cell.svelte +1 -1
  20. package/dist/data-table/cells/currency-cell.svelte +1 -1
  21. package/dist/data-table/cells/uuid-cell.svelte +15 -0
  22. package/dist/data-table/cells/uuid-cell.svelte.d.ts +8 -0
  23. package/dist/data-table/create-columns.js +17 -3
  24. package/dist/data-table/data-table-pagination.svelte +83 -42
  25. package/dist/data-table/data-table-svelte.svelte.js +4 -0
  26. package/dist/data-table/data-table-toolbar.svelte +6 -3
  27. package/dist/data-table/data-table-toolbar.svelte.d.ts +3 -0
  28. package/dist/data-table/data-table-types.d.ts +37 -7
  29. package/dist/data-table/data-table-view-options.svelte +69 -31
  30. package/dist/data-table/data-table-view-options.svelte.d.ts +2 -0
  31. package/dist/data-table/data-table.svelte +457 -93
  32. package/dist/data-table/table-setup.d.ts +7 -4
  33. package/dist/data-table/table-setup.js +29 -8
  34. package/dist/data-table/table-styles.d.ts +4 -4
  35. package/dist/data-table/table-styles.js +30 -11
  36. package/dist/index.d.ts +3 -1
  37. package/dist/index.js +4 -0
  38. package/dist/table/table-cell.svelte +1 -1
  39. package/dist/table/table-head.svelte +1 -1
  40. package/dist/table/table-row.svelte +1 -1
  41. package/dist/table/table.svelte +2 -2
  42. package/dist/tailwind.theme.css +14 -0
  43. package/dist/types.d.ts +38 -1
  44. package/package.json +4 -5
@@ -3,6 +3,7 @@
3
3
  import { createFloatingActions } from 'svelte-floating-ui'
4
4
  import { clickOutside } from './clickOutside.js'
5
5
  import { portal } from 'svelte-portal'
6
+ import { slide } from 'svelte/transition'
6
7
  import type { BaseDropdownProps } from './types.js'
7
8
 
8
9
  let {
@@ -10,12 +11,16 @@
10
11
  fullWidth = false,
11
12
  placement = 'bottom-start',
12
13
  matchParentWidth = false,
14
+ usePortal = true,
13
15
  class: className = '',
14
16
  trigger,
15
17
  children,
16
18
  ...rest
17
19
  }: BaseDropdownProps = $props()
18
20
 
21
+ // Conditional portal action - noop if disabled
22
+ const conditionalPortal = usePortal ? portal : () => {}
23
+
19
24
  const middleware = [offset(6), flip(), shift()]
20
25
 
21
26
  if (matchParentWidth) {
@@ -31,18 +36,24 @@
31
36
  )
32
37
  }
33
38
 
39
+ let closedFromClickOutside = $state(false)
40
+
41
+ // Create floating actions with strategy based on usePortal
42
+ const strategy = usePortal ? 'absolute' : 'fixed'
34
43
  const [floatingRef, floatingContent] = createFloatingActions({
35
- strategy: 'absolute',
44
+ strategy,
36
45
  placement,
37
46
  middleware
38
47
  })
39
48
 
40
- let closedFromClickOutside = $state(false)
41
-
42
49
  export const toggle = () => {
43
50
  isOpen = !isOpen
44
51
  }
45
52
 
53
+ export const close = () => {
54
+ isOpen = false
55
+ }
56
+
46
57
  function handleClick(event: MouseEvent) {
47
58
  event.stopPropagation()
48
59
  if (closedFromClickOutside) return
@@ -62,7 +73,7 @@
62
73
  {#if isOpen}
63
74
  <div
64
75
  class="max-h-40 absolute z-1001"
65
- use:portal
76
+ use:conditionalPortal
66
77
  use:floatingContent
67
78
  use:clickOutside
68
79
  onclick_outside={() => {
@@ -72,6 +83,7 @@
72
83
  }, 100)
73
84
  isOpen = false
74
85
  }}
86
+ transition:slide={{ duration: 100 }}
75
87
  >
76
88
  {@render children?.()}
77
89
  </div>
@@ -1,6 +1,7 @@
1
1
  import type { BaseDropdownProps } from './types.js';
2
2
  declare const BaseDropdown: import("svelte").Component<BaseDropdownProps, {
3
3
  toggle: () => void;
4
+ close: () => void;
4
5
  }, "isOpen">;
5
6
  type BaseDropdown = ReturnType<typeof BaseDropdown>;
6
7
  export default BaseDropdown;
@@ -50,7 +50,7 @@
50
50
  {/if}
51
51
  </span>
52
52
  {/snippet}
53
- <BaseDropdown bind:this={sortDropdown} {trigger} fullWidth>
53
+ <BaseDropdown bind:this={sortDropdown} {trigger} fullWidth usePortal={false}>
54
54
  <BaseTableHeaderOrderBy
55
55
  {sortDirection}
56
56
  isActive={sortBy === field.slug}
@@ -1,25 +1,33 @@
1
1
  <script lang="ts">
2
- import { SortAscending, SortDescending, Preview } from '@invopop/ui-icons'
2
+ import { SortAscending, SortDescending, Preview, Filter, Lock } from '@invopop/ui-icons'
3
3
  import type { TableSortBy, DrawerOption, BaseTableHeaderOrderByProps } from './types.js'
4
4
  import DrawerContext from './DrawerContext.svelte'
5
5
 
6
- let { isActive = false, sortDirection, onOrderBy, onHide }: BaseTableHeaderOrderByProps = $props()
6
+ let { isActive = false, sortDirection, onOrderBy, onHide, onFilter, onFreeze, isFrozen = false, showSortOptions = true, showFilterOption = true }: BaseTableHeaderOrderByProps = $props()
7
7
 
8
8
  let items = $derived([
9
- {
10
- icon: SortAscending,
11
- label: 'Sort Ascending',
12
- value: 'asc',
13
- selected: isActive && sortDirection === 'asc'
14
- },
15
- {
16
- icon: SortDescending,
17
- label: 'Sort Descending',
18
- value: 'desc',
19
- selected: isActive && sortDirection === 'desc'
20
- },
21
- { label: '', value: '', separator: true },
22
- { icon: Preview, label: 'Hide', value: 'hide' }
9
+ ...(showSortOptions ? [
10
+ {
11
+ icon: SortAscending,
12
+ label: 'Sort Ascending',
13
+ value: 'asc',
14
+ selected: isActive && sortDirection === 'asc'
15
+ },
16
+ {
17
+ icon: SortDescending,
18
+ label: 'Sort Descending',
19
+ value: 'desc',
20
+ selected: isActive && sortDirection === 'desc'
21
+ },
22
+ { label: '', value: 'sep-1', separator: true }
23
+ ] : []),
24
+ ...(showFilterOption ? [
25
+ { icon: Filter, label: 'Filter by column', value: 'filter' },
26
+ { label: '', value: 'sep-2', separator: true }
27
+ ] : []),
28
+ { icon: Lock, label: isFrozen ? 'Unfreeze column' : 'Freeze column', value: 'freeze' },
29
+ { label: '', value: 'sep-3', separator: true },
30
+ { icon: Preview, label: 'Hide column', value: 'hide' }
23
31
  ] as DrawerOption[])
24
32
  </script>
25
33
 
@@ -28,6 +36,10 @@
28
36
  onclick={(e) => {
29
37
  if (e === 'hide') {
30
38
  onHide?.()
39
+ } else if (e === 'filter') {
40
+ onFilter?.()
41
+ } else if (e === 'freeze') {
42
+ onFreeze?.()
31
43
  } else {
32
44
  onOrderBy?.(e as TableSortBy)
33
45
  }
@@ -0,0 +1,81 @@
1
+ <script lang="ts">
2
+ import { Search, Pulse } from '@invopop/ui-icons'
3
+ import BaseButton from './BaseButton.svelte'
4
+ import InputSearch from './InputSearch.svelte'
5
+ import { clickOutside } from './clickOutside.js'
6
+ import type { ButtonSearchProps } from './types.js'
7
+
8
+ let {
9
+ value = $bindable(''),
10
+ expanded = $bindable(false),
11
+ placeholder = 'Search...',
12
+ size = 'sm',
13
+ loading = false,
14
+ autofocus = false,
15
+ oninput,
16
+ onExpand,
17
+ onCollapse
18
+ }: ButtonSearchProps = $props()
19
+
20
+ let inputSearch: InputSearch | undefined = $state()
21
+ let isLoadingCollapsed = $derived(loading && !expanded)
22
+
23
+ function handleExpand() {
24
+ expanded = true
25
+ onExpand?.()
26
+ }
27
+
28
+ function handleClickOutside() {
29
+ if (expanded && value.trim() === '') {
30
+ expanded = false
31
+ onCollapse?.()
32
+ }
33
+ }
34
+
35
+ function handleInput(newValue: string) {
36
+ value = newValue
37
+ oninput?.(newValue)
38
+ }
39
+
40
+ $effect(() => {
41
+ if (expanded && inputSearch) {
42
+ inputSearch.focus()
43
+ }
44
+ })
45
+ </script>
46
+
47
+ <div
48
+ class="overflow-hidden transition-all duration-300 ease-in-out relative rounded-md"
49
+ style={expanded ? 'width: 12rem;' : 'width: 2.5rem;'}
50
+ use:clickOutside
51
+ onclick_outside={handleClickOutside}
52
+ >
53
+ <div
54
+ class="w-48 transition-opacity duration-200 absolute left-0 top-0"
55
+ class:opacity-0={!expanded}
56
+ class:opacity-100={expanded}
57
+ class:pointer-events-none={!expanded}
58
+ >
59
+ <InputSearch
60
+ bind:this={inputSearch}
61
+ bind:value
62
+ {placeholder}
63
+ {size}
64
+ {loading}
65
+ {autofocus}
66
+ oninput={handleInput}
67
+ />
68
+ </div>
69
+ <div
70
+ class="transition-opacity duration-200"
71
+ class:opacity-0={expanded}
72
+ class:opacity-100={!expanded}
73
+ class:pointer-events-none={expanded}
74
+ >
75
+ <BaseButton
76
+ icon={isLoadingCollapsed ? Pulse : Search}
77
+ class={isLoadingCollapsed ? 'pulse-icon' : ''}
78
+ onclick={handleExpand}
79
+ />
80
+ </div>
81
+ </div>
@@ -0,0 +1,4 @@
1
+ import type { ButtonSearchProps } from './types.js';
2
+ declare const ButtonSearch: import("svelte").Component<ButtonSearchProps, {}, "value" | "expanded">;
3
+ type ButtonSearch = ReturnType<typeof ButtonSearch>;
4
+ export default ButtonSearch;
@@ -3,13 +3,17 @@
3
3
  import RangeCalendar from './range-calendar/range-calendar.svelte'
4
4
  import { parseDate, type DateValue } from '@internationalized/date'
5
5
  import type { DateRange } from 'bits-ui'
6
- import { Icon } from '@steeze-ui/svelte-icon'
7
- import { Calendar } from '@invopop/ui-icons'
6
+ import { Icon, type IconSource } from '@steeze-ui/svelte-icon'
8
7
  import Transition from 'svelte-transition'
9
8
  import type { DatePickerProps } from './types'
10
9
  import { clickOutside } from './clickOutside'
11
10
  import BaseButton from './BaseButton.svelte'
12
- import { datesFromToday, toCalendarDate } from './helpers'
11
+ import { datesFromToday, toCalendarDate, resolveIcon } from './helpers'
12
+ import { buttonVariants } from './button/button.svelte'
13
+ import { offset, flip, shift } from 'svelte-floating-ui/dom'
14
+ import { createFloatingActions } from 'svelte-floating-ui'
15
+ import { portal } from 'svelte-portal'
16
+ import { cn } from './utils'
13
17
 
14
18
  const {
15
19
  startOfThisWeek,
@@ -109,25 +113,50 @@
109
113
 
110
114
  let {
111
115
  label = 'Date',
112
- position = 'left',
116
+ placement = 'bottom-start',
113
117
  from = '',
114
118
  to = '',
115
- onSelect
119
+ onSelect,
120
+ stackLeft = false,
121
+ stackRight = false,
122
+ icon = undefined,
123
+ iconTheme = 'default'
116
124
  }: DatePickerProps = $props()
117
125
 
126
+ const [floatingRef, floatingContent] = createFloatingActions({
127
+ strategy: 'absolute',
128
+ placement,
129
+ middleware: [offset(8), flip(), shift()]
130
+ })
131
+
132
+ let resolvedIcon: IconSource | undefined = $state()
133
+
134
+ $effect(() => {
135
+ resolveIcon(icon).then((res) => (resolvedIcon = res))
136
+ })
137
+
118
138
  let selectedPeriod = $state('custom')
119
139
  let value = $state<DateRange>({
120
140
  start: undefined,
121
141
  end: undefined
122
142
  })
123
143
  let isOpen = $state(false)
144
+ let selectedLabel = $state(label)
145
+ let isStacked = $derived(stackLeft || stackRight)
146
+ let hasSelectedDates = $derived(value.start !== undefined)
147
+ let hasConfirmedDates = $derived(selectedLabel !== label)
124
148
  let styles = $derived(
125
- clsx({
126
- 'border-border-selected-bold shadow-active': isOpen,
127
- 'border-border-secondary hover:border-border-default-secondary-hover': !isOpen
128
- })
149
+ isStacked
150
+ ? buttonVariants({
151
+ variant: 'ghost',
152
+ stackedLeft: stackLeft,
153
+ stackedRight: stackRight
154
+ })
155
+ : clsx('border backdrop-blur-sm backdrop-filter', {
156
+ 'border-border-selected-bold shadow-active': isOpen,
157
+ 'border-border-default-secondary hover:border-border-default-secondary-hover': !isOpen
158
+ })
129
159
  )
130
- let selectedLabel = $state(label)
131
160
 
132
161
  $effect(() => {
133
162
  if (!value.end) {
@@ -137,9 +166,19 @@
137
166
 
138
167
  $effect(() => {
139
168
  if (from) {
169
+ const startDate = parseDate(from)
170
+ const endDate = to ? parseDate(to) : undefined
171
+
140
172
  value = {
141
- start: parseDate(from),
142
- end: to ? parseDate(to) : undefined
173
+ start: startDate,
174
+ end: endDate
175
+ }
176
+
177
+ // Update label directly without calling getLabel() to avoid circular dependency
178
+ if (startDate === endDate) {
179
+ selectedLabel = getDisplayFromValue(startDate)
180
+ } else {
181
+ selectedLabel = `${getDisplayFromValue(startDate)} → ${getDisplayFromValue(endDate)}`
143
182
  }
144
183
  return
145
184
  }
@@ -183,19 +222,36 @@
183
222
  </script>
184
223
 
185
224
  <div>
186
- <div class="relative">
187
- <button
188
- onclick={() => {
189
- isOpen = !isOpen
190
- }}
191
- class="{styles} datepicker-trigger w-full py-1.25 pl-7 pr-2 text-left border border-border-default-secondary rounded-lg text-foreground placeholder-foreground text-base cursor-pointer"
225
+ <button
226
+ use:floatingRef
227
+ onclick={() => {
228
+ isOpen = !isOpen
229
+ }}
230
+ class="{styles} {isStacked
231
+ ? 'h-7 py-1.5'
232
+ : 'py-1.5'} datepicker-trigger flex items-center w-full {resolvedIcon
233
+ ? 'pl-7'
234
+ : 'pl-2'} pr-2 text-left rounded-lg bg-background cursor-pointer relative overflow-hidden"
235
+ >
236
+ {#if resolvedIcon}
237
+ <Icon
238
+ src={resolvedIcon}
239
+ theme={iconTheme}
240
+ class="h-4 w-4 absolute top-1.5 left-2 text-foreground-default-secondary"
241
+ />
242
+ {/if}
243
+ <span
244
+ class={clsx('flex-1 text-base truncate', {
245
+ 'text-foreground': hasConfirmedDates,
246
+ 'text-foreground-default-secondary': !hasConfirmedDates,
247
+ 'font-normal': isStacked && !hasConfirmedDates
248
+ })}
192
249
  >
193
250
  {selectedLabel}
194
- </button>
195
- <Icon src={Calendar} class="h-4 w-4 absolute top-2 left-2 text-foreground-default-secondary" />
196
- </div>
251
+ </span>
252
+ </button>
197
253
 
198
- <div class="relative">
254
+ {#if isOpen}
199
255
  <Transition
200
256
  show={isOpen}
201
257
  enter="transition ease-out duration-100"
@@ -207,9 +263,9 @@
207
263
  >
208
264
  <!-- @ts-ignore -->
209
265
  <div
210
- class:left-0={position === 'left'}
211
- class:right-0={position === 'right'}
212
- class="bg-background inline-flex flex-col shadow-lg rounded-xl absolute right-0 top-2 z-40 border border-border"
266
+ use:portal
267
+ use:floatingContent
268
+ class="bg-background inline-flex flex-col shadow-lg rounded-xl absolute z-1001 border border-border"
213
269
  use:clickOutside
214
270
  onclick_outside={() => {
215
271
  if (!isOpen) return
@@ -239,5 +295,5 @@
239
295
  </div>
240
296
  </div>
241
297
  </Transition>
242
- </div>
298
+ {/if}
243
299
  </div>