@invopop/popui 0.1.4-beta.5 → 0.1.4-beta.50

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 (51) hide show
  1. package/dist/BaseDropdown.svelte +42 -3
  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 +82 -0
  6. package/dist/ButtonSearch.svelte.d.ts +4 -0
  7. package/dist/DatePicker.svelte +84 -27
  8. package/dist/DatePicker.svelte.d.ts +5 -1
  9. package/dist/DrawerContext.svelte +428 -108
  10. package/dist/DrawerContextItem.svelte +6 -2
  11. package/dist/DropdownSelect.svelte +25 -6
  12. package/dist/DropdownSelect.svelte.d.ts +4 -1
  13. package/dist/EmptyState.svelte +6 -2
  14. package/dist/InputSearch.svelte +45 -5
  15. package/dist/InputSelect.svelte +12 -3
  16. package/dist/InputText.svelte +25 -8
  17. package/dist/InputToggle.svelte +23 -6
  18. package/dist/StepIcon.svelte +50 -0
  19. package/dist/StepIcon.svelte.d.ts +4 -0
  20. package/dist/StepIconList.svelte +24 -31
  21. package/dist/button/button.svelte +7 -0
  22. package/dist/button/button.svelte.d.ts +3 -0
  23. package/dist/data-table/cells/boolean-cell.svelte +1 -1
  24. package/dist/data-table/cells/currency-cell.svelte +1 -1
  25. package/dist/data-table/cells/uuid-cell.svelte +17 -0
  26. package/dist/data-table/cells/uuid-cell.svelte.d.ts +8 -0
  27. package/dist/data-table/column-definitions.js +3 -3
  28. package/dist/data-table/create-columns.js +18 -3
  29. package/dist/data-table/data-table-pagination.svelte +83 -42
  30. package/dist/data-table/data-table-toolbar.svelte +6 -3
  31. package/dist/data-table/data-table-toolbar.svelte.d.ts +3 -0
  32. package/dist/data-table/data-table-types.d.ts +55 -8
  33. package/dist/data-table/data-table-view-options.svelte +69 -31
  34. package/dist/data-table/data-table-view-options.svelte.d.ts +2 -0
  35. package/dist/data-table/data-table.svelte +475 -90
  36. package/dist/data-table/table-setup.d.ts +4 -0
  37. package/dist/data-table/table-setup.js +24 -2
  38. package/dist/data-table/table-styles.d.ts +4 -4
  39. package/dist/data-table/table-styles.js +30 -11
  40. package/dist/drawer-dnd-helpers.d.ts +30 -0
  41. package/dist/drawer-dnd-helpers.js +72 -0
  42. package/dist/index.d.ts +3 -1
  43. package/dist/index.js +4 -0
  44. package/dist/table/table-cell.svelte +1 -1
  45. package/dist/table/table-head.svelte +1 -1
  46. package/dist/table/table-header.svelte +1 -1
  47. package/dist/table/table-row.svelte +1 -1
  48. package/dist/table/table.svelte +2 -2
  49. package/dist/tailwind.theme.css +18 -4
  50. package/dist/types.d.ts +39 -1
  51. package/package.json +7 -5
@@ -4,18 +4,23 @@
4
4
  import { clickOutside } from './clickOutside.js'
5
5
  import { portal } from 'svelte-portal'
6
6
  import type { BaseDropdownProps } from './types.js'
7
+ import type { TransitionConfig } from 'svelte/transition'
7
8
 
8
9
  let {
9
10
  isOpen = $bindable(false),
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,51 @@
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)
49
+ // Custom transition that mimics shadcn style
50
+ function dropdownTransition(
51
+ node: HTMLElement,
52
+ { duration = 150 }: { duration?: number } = {}
53
+ ): TransitionConfig {
54
+ const side = placement.split('-')[0]
55
+
56
+ // Calculate slide direction
57
+ let slideY = 0
58
+ let slideX = 0
59
+ if (side === 'bottom') slideY = -8
60
+ if (side === 'top') slideY = 8
61
+ if (side === 'left') slideX = 8
62
+ if (side === 'right') slideX = -8
63
+
64
+ return {
65
+ duration,
66
+ css: (t) => {
67
+ const eased = t * (2 - t) // ease-out
68
+ return `
69
+ opacity: ${eased};
70
+ transform: scale(${0.95 + eased * 0.05}) translate(${slideX * (1 - eased)}px, ${slideY * (1 - eased)}px);
71
+ `
72
+ }
73
+ }
74
+ }
41
75
 
42
76
  export const toggle = () => {
43
77
  isOpen = !isOpen
44
78
  }
45
79
 
80
+ export const close = () => {
81
+ isOpen = false
82
+ }
83
+
46
84
  function handleClick(event: MouseEvent) {
47
85
  event.stopPropagation()
48
86
  if (closedFromClickOutside) return
@@ -62,7 +100,7 @@
62
100
  {#if isOpen}
63
101
  <div
64
102
  class="max-h-40 absolute z-1001"
65
- use:portal
103
+ use:conditionalPortal
66
104
  use:floatingContent
67
105
  use:clickOutside
68
106
  onclick_outside={() => {
@@ -72,6 +110,7 @@
72
110
  }, 100)
73
111
  isOpen = false
74
112
  }}
113
+ transition:dropdownTransition={{ duration: 150 }}
75
114
  >
76
115
  {@render children?.()}
77
116
  </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,82 @@
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-150 ease-in-out relative rounded-md"
49
+ class:w-[280px]={expanded}
50
+ class:w-10={!expanded}
51
+ use:clickOutside
52
+ onclick_outside={handleClickOutside}
53
+ >
54
+ <div
55
+ class="w-[280px] transition-opacity duration-100 absolute left-0 top-0"
56
+ class:opacity-0={!expanded}
57
+ class:opacity-100={expanded}
58
+ class:pointer-events-none={!expanded}
59
+ >
60
+ <InputSearch
61
+ bind:this={inputSearch}
62
+ bind:value
63
+ {placeholder}
64
+ {size}
65
+ {loading}
66
+ {autofocus}
67
+ oninput={handleInput}
68
+ />
69
+ </div>
70
+ <div
71
+ class="transition-opacity duration-100"
72
+ class:opacity-0={expanded}
73
+ class:opacity-100={!expanded}
74
+ class:pointer-events-none={expanded}
75
+ >
76
+ <BaseButton
77
+ icon={isLoadingCollapsed ? Pulse : Search}
78
+ class={isLoadingCollapsed ? 'pulse-icon' : ''}
79
+ onclick={handleExpand}
80
+ />
81
+ </div>
82
+ </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,14 +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'
13
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'
14
17
 
15
18
  const {
16
19
  startOfThisWeek,
@@ -110,21 +113,38 @@
110
113
 
111
114
  let {
112
115
  label = 'Date',
113
- position = 'left',
116
+ placement = 'bottom-start',
114
117
  from = '',
115
118
  to = '',
116
119
  onSelect,
117
120
  stackLeft = false,
118
- stackRight = false
121
+ stackRight = false,
122
+ icon = undefined,
123
+ iconTheme = 'default',
124
+ isOpen = $bindable(false)
119
125
  }: DatePickerProps = $props()
120
126
 
127
+ const [floatingRef, floatingContent] = createFloatingActions({
128
+ strategy: 'absolute',
129
+ placement,
130
+ middleware: [offset(8), flip(), shift()]
131
+ })
132
+
133
+ let resolvedIcon: IconSource | undefined = $state()
134
+
135
+ $effect(() => {
136
+ resolveIcon(icon).then((res) => (resolvedIcon = res))
137
+ })
138
+
121
139
  let selectedPeriod = $state('custom')
122
140
  let value = $state<DateRange>({
123
141
  start: undefined,
124
142
  end: undefined
125
143
  })
126
- let isOpen = $state(false)
144
+ let selectedLabel = $state(label)
127
145
  let isStacked = $derived(stackLeft || stackRight)
146
+ let hasSelectedDates = $derived(value.start !== undefined)
147
+ let hasConfirmedDates = $derived(selectedLabel !== label)
128
148
  let styles = $derived(
129
149
  isStacked
130
150
  ? buttonVariants({
@@ -132,12 +152,11 @@
132
152
  stackedLeft: stackLeft,
133
153
  stackedRight: stackRight
134
154
  })
135
- : clsx({
155
+ : clsx('border backdrop-blur-sm backdrop-filter', {
136
156
  'border-border-selected-bold shadow-active': isOpen,
137
- 'border-border-secondary hover:border-border-default-secondary-hover': !isOpen
157
+ 'border-border-default-secondary hover:border-border-default-secondary-hover': !isOpen
138
158
  })
139
159
  )
140
- let selectedLabel = $state(label)
141
160
 
142
161
  $effect(() => {
143
162
  if (!value.end) {
@@ -147,9 +166,19 @@
147
166
 
148
167
  $effect(() => {
149
168
  if (from) {
169
+ const startDate = parseDate(from)
170
+ const endDate = to ? parseDate(to) : undefined
171
+
150
172
  value = {
151
- start: parseDate(from),
152
- 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)}`
153
182
  }
154
183
  return
155
184
  }
@@ -190,24 +219,52 @@
190
219
 
191
220
  onSelect?.({ from: value.start?.toString() || '', to: value.end?.toString() || '' })
192
221
  }
222
+
223
+ // Exposed methods
224
+ export function open() {
225
+ isOpen = true
226
+ }
227
+
228
+ export function close() {
229
+ isOpen = false
230
+ }
231
+
232
+ export function toggle() {
233
+ isOpen = !isOpen
234
+ }
193
235
  </script>
194
236
 
195
237
  <div>
196
- <div class="relative">
197
- <button
198
- onclick={() => {
199
- isOpen = !isOpen
200
- }}
201
- class="{styles} {isStacked
202
- ? 'h-7 py-1.5'
203
- : 'py-1.25 border border-border-default-secondary'} datepicker-trigger w-full pl-7 pr-2 text-left rounded-lg text-foreground placeholder-foreground text-base cursor-pointer"
238
+ <button
239
+ use:floatingRef
240
+ onclick={() => {
241
+ isOpen = !isOpen
242
+ }}
243
+ class="{styles} {isStacked
244
+ ? 'h-7 py-1.5'
245
+ : 'py-1.5'} datepicker-trigger flex items-center w-full {resolvedIcon
246
+ ? 'pl-7'
247
+ : 'pl-2'} pr-2 text-left rounded-lg bg-background cursor-pointer relative overflow-hidden"
248
+ >
249
+ {#if resolvedIcon}
250
+ <Icon
251
+ src={resolvedIcon}
252
+ theme={iconTheme}
253
+ class="h-4 w-4 absolute top-1.5 left-2 text-foreground-default-secondary"
254
+ />
255
+ {/if}
256
+ <span
257
+ class={clsx('flex-1 text-base truncate', {
258
+ 'text-foreground': hasConfirmedDates,
259
+ 'text-foreground-default-secondary': !hasConfirmedDates,
260
+ 'font-normal': isStacked && !hasConfirmedDates
261
+ })}
204
262
  >
205
263
  {selectedLabel}
206
- </button>
207
- <Icon src={Calendar} class="h-4 w-4 absolute top-2 left-2 text-foreground-default-secondary" />
208
- </div>
264
+ </span>
265
+ </button>
209
266
 
210
- <div class="relative">
267
+ {#if isOpen}
211
268
  <Transition
212
269
  show={isOpen}
213
270
  enter="transition ease-out duration-100"
@@ -219,9 +276,9 @@
219
276
  >
220
277
  <!-- @ts-ignore -->
221
278
  <div
222
- class:left-0={position === 'left'}
223
- class:right-0={position === 'right'}
224
- class="bg-background inline-flex flex-col shadow-lg rounded-xl absolute right-0 top-2 z-40 border border-border"
279
+ use:portal
280
+ use:floatingContent
281
+ class="bg-background inline-flex flex-col shadow-lg rounded-xl absolute z-1001 border border-border"
225
282
  use:clickOutside
226
283
  onclick_outside={() => {
227
284
  if (!isOpen) return
@@ -251,5 +308,5 @@
251
308
  </div>
252
309
  </div>
253
310
  </Transition>
254
- </div>
311
+ {/if}
255
312
  </div>
@@ -1,4 +1,8 @@
1
1
  import type { DatePickerProps } from './types';
2
- declare const DatePicker: import("svelte").Component<DatePickerProps, {}, "">;
2
+ declare const DatePicker: import("svelte").Component<DatePickerProps, {
3
+ open: () => void;
4
+ close: () => void;
5
+ toggle: () => void;
6
+ }, "isOpen">;
3
7
  type DatePicker = ReturnType<typeof DatePicker>;
4
8
  export default DatePicker;