@invopop/popui 0.1.14 → 0.1.16

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 (103) hide show
  1. package/dist/BaseButton.svelte +25 -103
  2. package/dist/BaseCard.svelte +35 -30
  3. package/dist/BaseCounter.svelte +11 -8
  4. package/dist/BaseDropdown.svelte +3 -3
  5. package/dist/BaseTable.svelte +8 -12
  6. package/dist/BaseTableActions.svelte +4 -6
  7. package/dist/BaseTableCellContent.svelte +4 -6
  8. package/dist/BaseTableCheckbox.svelte +8 -10
  9. package/dist/BaseTableHeaderContent.svelte +4 -4
  10. package/dist/BaseTableRow.svelte +12 -10
  11. package/dist/Breadcrumb.svelte +40 -0
  12. package/dist/Breadcrumb.svelte.d.ts +4 -0
  13. package/dist/Breadcrumbs.svelte +5 -30
  14. package/dist/ButtonFile.svelte +35 -30
  15. package/dist/ButtonUuidCopy.svelte +3 -2
  16. package/dist/CardCheckbox.svelte +25 -21
  17. package/dist/CardRelation.svelte +12 -16
  18. package/dist/CompanySelector.svelte +35 -7
  19. package/dist/DataListItem.svelte +14 -10
  20. package/dist/DatePicker.svelte +14 -12
  21. package/dist/DrawerContext.svelte +111 -8
  22. package/dist/DrawerContextItem.svelte +18 -30
  23. package/dist/DrawerContextSeparator.svelte +1 -1
  24. package/dist/DrawerContextWorkspace.svelte +7 -7
  25. package/dist/DropdownSelect.svelte +38 -16
  26. package/dist/EmptyState.svelte +42 -0
  27. package/dist/EmptyState.svelte.d.ts +4 -0
  28. package/dist/EmptyStateIllustration.svelte.d.ts +0 -1
  29. package/dist/FeedEvents.svelte +9 -5
  30. package/dist/FeedIconEvent.svelte +1 -1
  31. package/dist/FeedIconStatus.svelte +1 -1
  32. package/dist/FeedItem.svelte +8 -8
  33. package/dist/FeedItemDetail.svelte +15 -6
  34. package/dist/GlobalSearch.svelte +13 -12
  35. package/dist/InputCheckbox.svelte +2 -5
  36. package/dist/InputError.svelte +4 -9
  37. package/dist/InputLabel.svelte +3 -1
  38. package/dist/InputRadio.svelte +26 -11
  39. package/dist/InputSearch.svelte +8 -8
  40. package/dist/InputSelect.svelte +32 -31
  41. package/dist/InputText.svelte +32 -24
  42. package/dist/InputTextarea.svelte +25 -19
  43. package/dist/InputToggle.svelte +24 -18
  44. package/dist/MenuItem.svelte +9 -8
  45. package/dist/MenuItemCollapsible.svelte +4 -4
  46. package/dist/Notification.svelte +59 -24
  47. package/dist/ProfileAvatar.svelte +43 -14
  48. package/dist/SeparatorHorizontal.svelte +2 -2
  49. package/dist/ShortcutWrapper.svelte +14 -5
  50. package/dist/StatusLabel.svelte +4 -5
  51. package/dist/StepIconList.svelte +11 -9
  52. package/dist/TagBeta.svelte +26 -14
  53. package/dist/TagStatus.svelte +37 -49
  54. package/dist/TitleMain.svelte +1 -1
  55. package/dist/TitleSection.svelte +1 -1
  56. package/dist/UuidCopy.svelte +4 -4
  57. package/dist/alert-dialog/alert-dialog-action.svelte +5 -3
  58. package/dist/alert-dialog/alert-dialog-cancel.svelte +4 -2
  59. package/dist/alert-dialog/alert-dialog-content.svelte +1 -1
  60. package/dist/alert-dialog/alert-dialog-description.svelte +1 -1
  61. package/dist/alert-dialog/alert-dialog-footer.svelte +1 -1
  62. package/dist/alert-dialog/alert-dialog-header.svelte +1 -1
  63. package/dist/alert-dialog/alert-dialog-title.svelte +1 -1
  64. package/dist/button/button.svelte +198 -24
  65. package/dist/button/button.svelte.d.ts +48 -26
  66. package/dist/index.d.ts +5 -10
  67. package/dist/index.js +3 -13
  68. package/dist/range-calendar/range-calendar-cell.svelte +1 -1
  69. package/dist/range-calendar/range-calendar-day.svelte +10 -8
  70. package/dist/range-calendar/range-calendar-head-cell.svelte +1 -1
  71. package/dist/range-calendar/range-calendar-next-button.svelte +3 -3
  72. package/dist/range-calendar/range-calendar-prev-button.svelte +3 -3
  73. package/dist/range-calendar/range-calendar.svelte +1 -1
  74. package/dist/sonner/sonner.svelte +7 -9
  75. package/dist/svg/CheckBadge.svelte +18 -0
  76. package/dist/svg/CheckBadge.svelte.d.ts +26 -0
  77. package/dist/svg/IconEmpty.svelte +78 -106
  78. package/dist/table/table-body.svelte +1 -1
  79. package/dist/table/table-cell.svelte +1 -1
  80. package/dist/table/table-footer.svelte +1 -1
  81. package/dist/table/table-head.svelte +1 -1
  82. package/dist/table/table-header.svelte +1 -1
  83. package/dist/table/table-row.svelte +1 -1
  84. package/dist/tabs/tabs-list.svelte +8 -2
  85. package/dist/tabs/tabs-list.svelte.d.ts +4 -1
  86. package/dist/tabs/tabs-trigger.svelte +5 -3
  87. package/dist/tabs/tabs-trigger.svelte.d.ts +4 -1
  88. package/dist/tailwind.theme.css +981 -0
  89. package/dist/tooltip/tooltip-content.svelte +2 -2
  90. package/dist/types.d.ts +36 -42
  91. package/package.json +2 -2
  92. package/dist/CounterWorkflow.svelte +0 -19
  93. package/dist/CounterWorkflow.svelte.d.ts +0 -4
  94. package/dist/EmptyStateIcon.svelte +0 -52
  95. package/dist/EmptyStateIcon.svelte.d.ts +0 -4
  96. package/dist/FormLayoutModal.svelte +0 -14
  97. package/dist/FormLayoutModal.svelte.d.ts +0 -4
  98. package/dist/ProfileSelector.svelte +0 -41
  99. package/dist/ProfileSelector.svelte.d.ts +0 -4
  100. package/dist/SectionLayout.svelte +0 -13
  101. package/dist/SectionLayout.svelte.d.ts +0 -4
  102. package/dist/tw.theme.d.ts +0 -171
  103. package/dist/tw.theme.js +0 -188
@@ -1,32 +1,29 @@
1
1
  <script lang="ts">
2
- import { Icon } from '@steeze-ui/svelte-icon'
3
- import { Invoice, Download } from '@invopop/ui-icons'
4
- import BaseButton from './BaseButton.svelte'
2
+ import { Download } from '@invopop/ui-icons'
3
+ import Button from './button/button.svelte'
5
4
  import type { ButtonFileProps } from './types.js'
6
- import clsx from 'clsx'
5
+ import { cn } from './utils.js'
7
6
 
8
7
  let {
9
- icon = Invoice,
10
8
  name = '',
11
9
  disabled = false,
12
10
  date = '',
13
- iconColor = 'grey',
11
+ fileType = 'pdf',
14
12
  onPreview,
15
13
  onDownload,
14
+ class: className,
16
15
  ...rest
17
16
  }: ButtonFileProps = $props()
18
17
 
19
- let iconStyles = $derived(
20
- clsx({
21
- 'border-positive-100 bg-positive-50 text-positive-500': iconColor === 'green',
22
- 'border-yellow-100 bg-yellow-50 text-yellow-500': iconColor === 'yellow',
23
- 'border-red-100 bg-red-50 text-red-500': iconColor === 'red',
24
- 'border-warning-100 bg-warning-50 text-warning-500': iconColor === 'orange',
25
- 'border-blue-100 bg-blue-50 text-blue-500': iconColor === 'blue',
26
- 'border-purple-100 bg-purple-50 text-purple-500': iconColor === 'purple',
27
- 'border-dashed border-neutral-100 text-neutral-400': iconColor === 'empty',
28
- 'border-neutral-100 bg-neutral-50 text-neutral-500': iconColor === 'grey'
29
- })
18
+ let fileAvatarStyles = $derived(
19
+ cn(
20
+ 'size-8 rounded-md border border-border flex items-center justify-center text-xs font-black font-mono leading-4 uppercase',
21
+ {
22
+ 'text-foreground-document-pdf': fileType === 'pdf',
23
+ 'text-foreground-document-xml': fileType === 'xml',
24
+ 'text-foreground-document-png': fileType === 'png'
25
+ }
26
+ )
30
27
  )
31
28
 
32
29
  function handleClick(event: MouseEvent) {
@@ -36,27 +33,35 @@
36
33
  </script>
37
34
 
38
35
  <button
39
- class:opacity-40={disabled}
40
- class="border border-neutral-100 hover:bg-neutral-50 active:bg-neutral-100 hover:border-neutral-200 active:border-neutral-300 rounded-lg flex items-center space-x-3 py-1.5 pr-3 pl-2.5 w-full cursor-pointer"
36
+ class={cn(
37
+ 'flex items-center gap-3 px-2 py-1.5 rounded-[10px] w-full hover:bg-background-default-secondary cursor-pointer',
38
+ disabled && 'opacity-30 pointer-events-none',
39
+ className
40
+ )}
41
41
  {disabled}
42
42
  {...rest}
43
43
  onclick={handleClick}
44
44
  >
45
- <span class="flex items-center justify-start space-x-2.5 flex-1">
46
- <div class="{iconStyles} p-2 border rounded">
47
- <Icon src={icon} class="w-4 h-4" />
45
+ <div class="flex items-center gap-[10px] flex-1 min-w-0">
46
+ <div class={fileAvatarStyles}>
47
+ {fileType}
48
48
  </div>
49
- <div class="flex flex-col items-start space-y-0.5">
50
- <span class="text-base font-medium text-neutral-800 tracking-tight max-w-[174px] truncate">
49
+ <div class="flex flex-col text-start min-w-0 flex-1">
50
+ <div class="text-sm font-medium text-foreground truncate w-full">
51
51
  {name}
52
- </span>
53
- <span class="text-sm text-neutral-500">{date}</span>
52
+ </div>
53
+ <div class="text-xs text-foreground-default-secondary truncate w-full">
54
+ {date}
55
+ </div>
54
56
  </div>
55
- </span>
56
- <BaseButton
57
- {disabled}
57
+ </div>
58
+ <Button
59
+ variant="secondary"
60
+ size="md"
58
61
  icon={Download}
59
- onclick={() => {
62
+ {disabled}
63
+ onclick={(e) => {
64
+ e.stopPropagation()
60
65
  onDownload?.()
61
66
  }}
62
67
  />
@@ -28,13 +28,14 @@
28
28
 
29
29
  <BaseButton
30
30
  {disabled}
31
- big
31
+ size="sm"
32
32
  icon={Duplicate}
33
33
  iconPosition="right"
34
+ variant="ghost"
34
35
  onclick={async () => {
35
36
  await navigator.clipboard.writeText(uuid)
36
37
  oncopied?.(uuid)
37
38
  }}
38
39
  >
39
- <span class="font-mono text-neutral-500">{formattedUuid}</span>
40
+ <span class="font-mono">{formattedUuid}</span>
40
41
  </BaseButton>
@@ -11,47 +11,51 @@
11
11
  description = '',
12
12
  accentText = '',
13
13
  checked = false,
14
+ disabled = false,
14
15
  icon = undefined,
15
16
  hideRadio = false,
16
17
  footer,
17
18
  onchange
18
19
  }: CardCheckboxProps = $props()
19
20
 
20
- let styles = $derived(
21
- clsx(
22
- { 'border-workspace-accent shadow-active': checked },
23
- { 'border-neutral-200 hover:border-neutral-300': !checked }
24
- )
21
+ let containerStyles = $derived(
22
+ clsx('border gap-3 py-2 pr-2 pl-3 flex items-start rounded-xl', {
23
+ 'border-foreground-selected': checked,
24
+ 'border-border': !checked,
25
+ 'bg-background-default-secondary': disabled
26
+ })
25
27
  )
26
28
  </script>
27
29
 
28
- <label for={id} class="{styles} border rounded-lg w-full text-left cursor-pointer block">
29
- <div class="py-2 pr-2 pl-3 flex items-start justify-between">
30
- <div class="flex space-x-2">
30
+ <label for={id} class="cursor-pointer">
31
+ <div class={containerStyles}>
32
+ <div class="flex grow items-start gap-1 min-w-0">
31
33
  {#if icon}
32
- <Icon src={icon} class="h-5 w-5 text-neutral-500 flex-shrink-0" />
34
+ <Icon src={icon} class="size-4 text-icon shrink-0 mt-0.5" />
33
35
  {/if}
34
- <div class="flex flex-col space-y-0.5">
35
- <span class="text-base text-neutral-800 font-medium">{title}</span>
36
+ <div class="flex flex-col gap-1 min-w-0">
37
+ <span class="text-base font-medium text-foreground">
38
+ {title}
39
+ </span>
36
40
  {#if description}
37
- <span class="flex items-center space-x-1">
41
+ <span class="text-sm text-foreground-default-secondary">
38
42
  {#if accentText}
39
- <p class="text-workspace-accent text-sm font-bold">{accentText}</p>
43
+ <span class="font-medium text-foreground-accent">{accentText}</span>
44
+ {' · '}
40
45
  {/if}
41
- <p class="text-sm text-neutral-500" class:first-letter:uppercase={!accentText}>
42
- {description}
43
- </p>
46
+ {description}
44
47
  </span>
45
48
  {/if}
46
49
  </div>
47
50
  </div>
48
-
49
- <div class:hidden={hideRadio}>
50
- <InputRadio {id} {name} {checked} {onchange} />
51
- </div>
51
+ {#if !hideRadio}
52
+ <div class="flex items-center p-px">
53
+ <InputRadio {id} {name} {checked} {disabled} {onchange} />
54
+ </div>
55
+ {/if}
52
56
  </div>
53
57
  {#if footer}
54
- <div class="bg-neutral-50 rounded-b-lg px-3 py-2.5 border-t border-neutral-100">
58
+ <div class="border-t border-border bg-background-default-secondary rounded-b-xl px-3 pb-3 pt-2">
55
59
  {@render footer?.()}
56
60
  </div>
57
61
  {/if}
@@ -2,31 +2,27 @@
2
2
  import { Icon } from '@steeze-ui/svelte-icon'
3
3
  import type { CardRelationProps } from './types.js'
4
4
  import { ChevronRight } from '@invopop/ui-icons'
5
- import SeparatorHorizontal from './SeparatorHorizontal.svelte'
6
5
 
7
6
  let { title = '', icon = undefined, items = [], onclick }: CardRelationProps = $props()
8
7
  </script>
9
8
 
10
- <div class="border border-neutral-100 rounded-lg">
11
- <button
12
- class="cursor-pointer pl-3 py-2 pr-2 flex items-center justify-between space-x-3 w-full"
13
- {onclick}
14
- >
15
- <div class="flex items-center space-x-1.5">
9
+ <div class="border border-border-default-secondary rounded-2xl overflow-hidden">
10
+ <button class="flex items-center gap-3 px-3 py-2 w-full cursor-pointer" {onclick}>
11
+ <div class="flex grow items-center gap-1.5 min-w-0">
16
12
  {#if icon}
17
- <Icon src={icon} class="h-4 w-4 text-neutral-500" />
13
+ <Icon src={icon} class="size-4 text-icon shrink-0" />
18
14
  {/if}
19
- <span class="text-base font-medium text-neutral-800">{title}</span>
15
+ <span class="text-base font-medium text-foreground whitespace-nowrap">
16
+ {title}
17
+ </span>
20
18
  </div>
21
-
22
- <Icon src={ChevronRight} class="h-4 w-4 text-neutral-500" />
19
+ <Icon src={ChevronRight} class="size-3 text-icon shrink-0" />
23
20
  </button>
24
- <SeparatorHorizontal />
25
- <div class="py-1.5 text-sm">
21
+ <div class="flex flex-col gap-2 px-3 pb-3 pt-2">
26
22
  {#each items as item}
27
- <div class="px-3 py-1 flex items-center space-x-3">
28
- <div class="min-w-[88px] text-neutral-500">{item.label}</div>
29
- <div class="text-neutral-800">{item.value}</div>
23
+ <div class="flex items-center gap-3 text-sm">
24
+ <div class="min-w-[88px] text-foreground-default-secondary shrink-0">{item.label}</div>
25
+ <div class="grow text-foreground min-w-0">{item.value}</div>
30
26
  </div>
31
27
  {/each}
32
28
  </div>
@@ -1,9 +1,9 @@
1
1
  <script lang="ts">
2
2
  import ProfileAvatar from './ProfileAvatar.svelte'
3
- import type { AnyProp, CompanySelectorProps, DrawerOption } from './types.js'
3
+ import type { AnyProp, CompanySelectorProps, DrawerOption, DrawerGroup } from './types.js'
4
4
  import BaseDropdown from './BaseDropdown.svelte'
5
- import DrawerContextWorkspace from './DrawerContextWorkspace.svelte'
6
- import { DoubleArrow } from '@invopop/ui-icons'
5
+ import DrawerContext from './DrawerContext.svelte'
6
+ import { DoubleArrow, Workspace, AddCircle, ExternalLink } from '@invopop/ui-icons'
7
7
  import MenuItemCollapsible from './MenuItemCollapsible.svelte'
8
8
 
9
9
  let companyDropdown: BaseDropdown | undefined = $state()
@@ -21,6 +21,24 @@
21
21
  let country = $derived(selectedCompany?.country || '')
22
22
  let picture = $derived(selectedCompany?.logo_url || '')
23
23
  let isSandbox = $derived(selectedCompany?.sandbox)
24
+
25
+ let groups: DrawerGroup[] = [
26
+ {
27
+ label: 'Live',
28
+ slug: 'live',
29
+ emptyIcon: Workspace,
30
+ emptyTitle: 'No workspaces here',
31
+ emptyDescription: 'Create a workspace to start'
32
+ },
33
+ {
34
+ label: 'Sandbox',
35
+ slug: 'sandbox',
36
+ emptyIcon: Workspace,
37
+ emptyTitle: 'No workspaces here',
38
+ emptyDescription: 'Create a workspace to start'
39
+ }
40
+ ]
41
+
24
42
  let items = $derived([
25
43
  ...companies.map((c) => ({
26
44
  value: c.id,
@@ -28,8 +46,18 @@
28
46
  selected: c.slug === selectedCompany?.slug && !!c.sandbox === !!selectedCompany?.sandbox,
29
47
  country: c.country,
30
48
  picture: c.logo_url,
31
- sandbox: c.sandbox
32
- }))
49
+ sandbox: c.sandbox,
50
+ groupBy: c.sandbox ? 'sandbox' : 'live'
51
+ })),
52
+ {
53
+ separator: true
54
+ },
55
+ {
56
+ value: 'add',
57
+ label: 'Create workspace',
58
+ icon: AddCircle,
59
+ rightIcon: ExternalLink
60
+ }
33
61
  ] as DrawerOption[])
34
62
 
35
63
  function selectCompany(value: AnyProp) {
@@ -60,8 +88,8 @@
60
88
  active={isOpen}
61
89
  bold
62
90
  >
63
- <ProfileAvatar {name} {picture} {country} dark large />
91
+ <ProfileAvatar {name} {picture} {country} dark variant="lg" />
64
92
  </MenuItemCollapsible>
65
93
  {/snippet}
66
- <DrawerContextWorkspace {items} onclick={selectCompany} />
94
+ <DrawerContext {items} {groups} onclick={selectCompany} widthClass="w-[300px]" />
67
95
  </BaseDropdown>
@@ -11,8 +11,8 @@
11
11
  children
12
12
  }: DataListItemProps = $props()
13
13
 
14
- let styles = $derived(
15
- clsx({
14
+ let valueStyles = $derived(
15
+ clsx('text-foreground font-medium text-base', {
16
16
  'font-mono': monospaced,
17
17
  'slashed-zero tabular-nums lining-nums': monospacedNums,
18
18
  'w-full': fullWidth
@@ -20,13 +20,17 @@
20
20
  )
21
21
  </script>
22
22
 
23
- <div class="flex space-x-4 text-base items-center">
24
- <div class="text-neutral-500 min-w-[125px]">{label}</div>
25
- <div class="{styles} text-neutral-800 font-medium">
26
- {#if children}
27
- {@render children()}
28
- {:else}
29
- {value}
30
- {/if}
23
+ <div class="flex gap-6 items-center px-3 py-1.5 rounded-lg hover:bg-background-default-secondary">
24
+ <div class="text-foreground-default-secondary text-base min-w-[125px]">
25
+ {label}
26
+ </div>
27
+ <div class="flex gap-1 items-center">
28
+ <div class={valueStyles}>
29
+ {#if children}
30
+ {@render children()}
31
+ {:else}
32
+ {value}
33
+ {/if}
34
+ </div>
31
35
  </div>
32
36
  </div>
@@ -120,11 +120,11 @@
120
120
  start: undefined,
121
121
  end: undefined
122
122
  })
123
- let isOpen = $state(false)
123
+ let isOpen = $state(true)
124
124
  let styles = $derived(
125
125
  clsx({
126
- 'border-workspace-accent focus:border-workspace-accent shadow-active': isOpen,
127
- 'border-neutral-200 hover:border-neutral-300': !isOpen
126
+ 'border-border-selected-bold shadow-active': isOpen,
127
+ 'border-border hover:border-border-default-secondary-hover': !isOpen
128
128
  })
129
129
  )
130
130
  let selectedLabel = $state(label)
@@ -188,11 +188,11 @@
188
188
  onclick={() => {
189
189
  isOpen = !isOpen
190
190
  }}
191
- class="{styles} datepicker-trigger w-full py-1.25 pl-7 pr-8 border rounded-md text-neutral-800 placeholder-neutral-800 text-base cursor-pointer"
191
+ class="{styles} datepicker-trigger w-full py-1.25 pl-7 pr-8 border rounded-lg text-foreground placeholder-foreground text-base cursor-pointer"
192
192
  >
193
193
  {selectedLabel}
194
194
  </button>
195
- <Icon src={Calendar} class="h-4 w-4 absolute top-2 left-2 text-neutral-500" />
195
+ <Icon src={Calendar} class="h-4 w-4 absolute top-2 left-2 text-foreground-default-secondary" />
196
196
  </div>
197
197
 
198
198
  <div class="relative">
@@ -209,21 +209,21 @@
209
209
  <div
210
210
  class:left-0={position === 'left'}
211
211
  class:right-0={position === 'right'}
212
- class="bg-white inline-flex flex-col shadow rounded-lg absolute right-0 top-2 z-40"
212
+ class="bg-white inline-flex flex-col shadow-lg rounded-xl absolute right-0 top-2 z-40 border border-border"
213
213
  use:clickOutside
214
214
  onclick_outside={() => {
215
215
  if (!isOpen) return
216
216
  cancel()
217
217
  }}
218
218
  >
219
- <div class="flex border-b border-neutral-100 min-h-[300px] rounded-lg shadow-calendar">
220
- <div class="flex flex-col space-y-2 items-start p-3 border-r border-neutral-100">
219
+ <div class="flex border-b border-border min-h-[300px] shadow-calendar">
220
+ <div class="flex flex-col space-y-2 items-start p-3 border-r border-border">
221
221
  {#each periods as period}
222
222
  <button
223
223
  onclick={period.action}
224
224
  class="{selectedPeriod === period.slug
225
- ? 'selected-period text-workspace-accent-600 bg-workspace-accent-100'
226
- : 'text-neutral-500'} whitespace-nowrap text-base px-2 py-1 tracking-normal rounded cursor-pointer"
225
+ ? 'selected-period text-foreground-selected bg-background-selected font-medium'
226
+ : 'text-foreground-default-secondary'} whitespace-nowrap text-base px-2 py-1 tracking-normal rounded-md cursor-pointer"
227
227
  >
228
228
  {period.label}
229
229
  </button>
@@ -232,8 +232,10 @@
232
232
  <RangeCalendar bind:value numberOfMonths={2} />
233
233
  </div>
234
234
  <div class="p-3 flex justify-end items-center space-x-3">
235
- <BaseButton variant="secondary" onclick={cancel}>Cancel</BaseButton>
236
- <BaseButton variant="primary" onclick={confirm} disabled={!value.end}>Confirm</BaseButton>
235
+ <BaseButton variant="secondary" size="lg" onclick={cancel}>Cancel</BaseButton>
236
+ <BaseButton variant="primary" size="lg" onclick={confirm} disabled={!value.end}
237
+ >Confirm</BaseButton
238
+ >
237
239
  </div>
238
240
  </div>
239
241
  </Transition>
@@ -2,6 +2,11 @@
2
2
  import type { DrawerContextProps, DrawerOption } from './types.ts'
3
3
  import DrawerContextItem from './DrawerContextItem.svelte'
4
4
  import DrawerContextSeparator from './DrawerContextSeparator.svelte'
5
+ import EmptyState from './EmptyState.svelte'
6
+ import BaseCounter from './BaseCounter.svelte'
7
+ import { Icon } from '@steeze-ui/svelte-icon'
8
+ import { ChevronRight } from '@steeze-ui/heroicons'
9
+ import { slide } from 'svelte/transition'
5
10
 
6
11
  let {
7
12
  items = $bindable([]),
@@ -9,10 +14,44 @@
9
14
  widthClass = 'w-60',
10
15
  onclick,
11
16
  onselect,
12
- children
17
+ children,
18
+ groups
13
19
  }: DrawerContextProps = $props()
14
20
 
15
21
  let selectedItems = $derived(items.filter((i) => i.selected))
22
+ let hasGroups = $derived(groups && groups.length > 0)
23
+ let { groupedItems, ungroupedItems } = $derived.by(() => {
24
+ if (!hasGroups) return { groupedItems: new Map(), ungroupedItems: items }
25
+
26
+ const grouped = new Map<string, DrawerOption[]>()
27
+ const ungrouped: DrawerOption[] = []
28
+
29
+ groups!.forEach((group) => {
30
+ grouped.set(group.slug, [])
31
+ })
32
+
33
+ items.forEach((item) => {
34
+ if (item.groupBy && grouped.has(item.groupBy)) {
35
+ grouped.get(item.groupBy)!.push(item)
36
+ } else {
37
+ ungrouped.push(item)
38
+ }
39
+ })
40
+
41
+ return { groupedItems: grouped, ungroupedItems: ungrouped }
42
+ })
43
+
44
+ let openGroups = $state<Record<string, boolean>>({})
45
+
46
+ $effect(() => {
47
+ if (hasGroups) {
48
+ const selectedItem = items.find((i) => i.selected)
49
+ if (selectedItem?.groupBy && Object.keys(openGroups).length === 0) {
50
+ openGroups = { [selectedItem.groupBy]: true }
51
+ }
52
+ }
53
+ })
54
+
16
55
  $effect(() => {
17
56
  onselect?.(selectedItems)
18
57
  })
@@ -23,19 +62,83 @@
23
62
  return i
24
63
  })
25
64
  }
65
+
66
+ function toggleGroup(groupSlug: string) {
67
+ openGroups = openGroups[groupSlug] ? {} : { [groupSlug]: true }
68
+ }
26
69
  </script>
27
70
 
71
+ {#snippet drawerItem(item: DrawerOption)}
72
+ {#if item.separator}
73
+ <DrawerContextSeparator />
74
+ {:else}
75
+ <div class:px-1={!item.groupBy}>
76
+ <DrawerContextItem {item} {multiple} {onclick} onchange={updateItem} />
77
+ </div>
78
+ {/if}
79
+ {/snippet}
80
+
28
81
  <div
29
- class="{widthClass} border border-neutral-200 py-0.5 rounded-2xl shadow-lg space-y-0.5 bg-white max-h-80 overflow-y-auto"
82
+ class="{widthClass} border border-border rounded-2xl shadow-lg bg-white overflow-hidden flex flex-col py-1 max-h-[400px] overflow-y-auto"
30
83
  >
31
84
  {@render children?.()}
32
- <ul class="space-y-0.5 max-h-80 overflow-y-auto">
33
- {#each items as item}
34
- {#if item.separator}
85
+
86
+ {#if hasGroups}
87
+ {#each groups as group, index}
88
+ {@const groupItems = groupedItems.get(group.slug) || []}
89
+ {@const isLastGroup = index === groups!.length - 1}
90
+ <div class="flex-shrink-0 px-1">
91
+ <button
92
+ class="cursor-pointer flex items-center justify-between h-8 pl-2.5 pr-2.5 py-2.5 text-base font-medium text-foreground-default-secondary w-full hover:bg-background-default-secondary rounded-lg overflow-clip"
93
+ onclick={() => toggleGroup(group.slug)}
94
+ >
95
+ <div class="flex items-center gap-1.5">
96
+ <span>{group.label}</span>
97
+ <Icon
98
+ src={ChevronRight}
99
+ class="size-3 text-icon-default-secondary transition-all transform {openGroups[
100
+ group.slug
101
+ ]
102
+ ? 'rotate-90'
103
+ : ''}"
104
+ />
105
+ </div>
106
+ {#if groupItems.length}
107
+ <BaseCounter value={groupItems.length} />
108
+ {/if}
109
+ </button>
110
+
111
+ {#if openGroups[group.slug]}
112
+ <div transition:slide class="w-full">
113
+ {#if !groupItems.length}
114
+ <div class="px-1 pt-1 pb-5">
115
+ <EmptyState
116
+ iconSource={group.emptyIcon}
117
+ title={group.emptyTitle || 'No items here'}
118
+ description={group.emptyDescription || 'Add items to get started'}
119
+ />
120
+ </div>
121
+ {:else}
122
+ <div class="max-h-[400px] overflow-y-auto">
123
+ {#each groupItems as item}
124
+ {@render drawerItem(item)}
125
+ {/each}
126
+ </div>
127
+ {/if}
128
+ </div>
129
+ {/if}
130
+ </div>
131
+ {#if !isLastGroup}
35
132
  <DrawerContextSeparator />
36
- {:else}
37
- <DrawerContextItem {item} {multiple} {onclick} onchange={updateItem} />
38
133
  {/if}
39
134
  {/each}
40
- </ul>
135
+ {/if}
136
+
137
+ {#if ungroupedItems.length}
138
+ <div class="flex-shrink-0">
139
+ {#each ungroupedItems as item}
140
+ {@render drawerItem(item)}
141
+ {/each}
142
+ </div>
143
+ {/if}
41
144
  </div>
@@ -3,8 +3,7 @@
3
3
  import InputCheckbox from './InputCheckbox.svelte'
4
4
  import { Icon } from '@steeze-ui/svelte-icon'
5
5
  import { onMount } from 'svelte'
6
- import { Success, Tick } from '@invopop/ui-icons'
7
- import ProfileAvatar from './ProfileAvatar.svelte'
6
+ import { Success } from '@invopop/ui-icons'
8
7
  import clsx from 'clsx'
9
8
  import BaseFlag from './BaseFlag.svelte'
10
9
  import { getCountryName } from './helpers.js'
@@ -14,32 +13,24 @@
14
13
  multiple = false,
15
14
  item = $bindable(),
16
15
  scrollIfSelected = false,
17
- workspace = false,
18
16
  onchange,
19
17
  onclick
20
18
  }: DrawerContextItemProps = $props()
21
19
 
22
20
  let el: HTMLElement | undefined = $state()
23
21
 
24
- let hasIcon = $derived(item.icon || workspace)
25
-
26
22
  let styles = $derived(
27
23
  clsx(
28
- { 'py-1 space-x-3': workspace },
29
- { 'py-1.5 space-x-1.5': !workspace },
30
- { 'pl-1.5': !hasIcon },
31
- { 'pl-2': hasIcon },
32
- { 'bg-workspace-accent-100': item.selected && !multiple },
33
- { 'group-hover:bg-neutral-100': (!item.selected && !item.disabled) || multiple }
24
+ 'px-2 py-1.5 space-x-1.5',
25
+ { 'bg-background-selected': item.selected && !multiple },
26
+ {
27
+ 'group-hover:bg-background-default-secondary':
28
+ (!item.selected && !item.disabled) || multiple
29
+ }
34
30
  )
35
31
  )
36
32
  let labelStyles = $derived(
37
- clsx(
38
- { 'text-danger-500': item.destructive },
39
- { 'text-neutral-800': !item.destructive },
40
- { 'tracking-tight max-w-[200px]': workspace },
41
- { 'tracking-normal': !workspace }
42
- )
33
+ clsx({ 'text-danger-500': item.destructive }, { 'text-neutral-800': !item.destructive })
43
34
  )
44
35
  let title = $derived(item.label.length > 25 ? item.label : undefined)
45
36
 
@@ -64,19 +55,15 @@
64
55
 
65
56
  <button
66
57
  bind:this={el}
67
- class="cursor-pointer w-full px-1 py-0.5 disabled:opacity-30 group"
58
+ class="cursor-pointer w-full disabled:opacity-30 group"
68
59
  disabled={item.disabled}
69
60
  onclick={handleClick}
70
61
  >
71
- <div class="{styles} rounded pr-2 flex items-center justify-start w-full">
72
- {#if workspace}
73
- <ProfileAvatar name={item.label} picture={item.picture} large />
74
- {:else if item.icon}
62
+ <div class="{styles} rounded-md pr-2 flex items-center justify-start w-full">
63
+ {#if item.icon}
75
64
  <Icon
76
65
  src={item.icon}
77
- class="w-4 h-4 {item.destructive
78
- ? 'text-danger-500'
79
- : item.iconClass || 'text-neutral-500'}"
66
+ class="w-4 h-4 {item.destructive ? 'text-icon-critical' : item.iconClass || 'text-icon'}"
80
67
  />
81
68
  {/if}
82
69
  <div class="whitespace-nowrap flex-1 text-left flex flex-col truncate" {title}>
@@ -90,7 +77,7 @@
90
77
  {#if item.country}
91
78
  <span class="flex space-x-1 items-center">
92
79
  <BaseFlag country={item.country} width={10} />
93
- <span class="text-sm text-neutral-500 tracking-normal">
80
+ <span class="text-sm text-foreground-default-secondary">
94
81
  {getCountryName(item.country)}
95
82
  </span>
96
83
  </span>
@@ -98,15 +85,16 @@
98
85
  </div>
99
86
  {#if multiple}
100
87
  <InputCheckbox
101
- bind:checked={item.selected}
102
- onchange={() => {
88
+ checked={item.selected ?? false}
89
+ onchange={(value) => {
90
+ item.selected = value
103
91
  onchange?.(item)
104
92
  }}
105
93
  />
106
94
  {:else if item.selected}
107
- <Icon src={Success} class="w-4 h-4 text-workspace-accent" />
95
+ <Icon src={Success} class="size-4 text-icon-selected" />
108
96
  {:else if item.rightIcon}
109
- <Icon src={item.rightIcon} class="w-4 h-4 text-neutral-400" />
97
+ <Icon src={item.rightIcon} class="size-4 text-icon-default-secondary" />
110
98
  {/if}
111
99
  </div>
112
100
  </button>
@@ -1 +1 @@
1
- <li class="bg-neutral-100 h-px w-full"></li>
1
+ <li class="bg-border h-px w-full my-1"></li>