@globalbrain/sefirot 2.0.0-draft.8 → 2.1.0

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 (182) hide show
  1. package/README.md +6 -6
  2. package/lib/components/SAvatar.vue +17 -17
  3. package/lib/components/SButton.vue +520 -276
  4. package/lib/components/SButtonGroup.vue +149 -0
  5. package/lib/components/SDropdown.vue +26 -150
  6. package/lib/components/SDropdownSection.vue +48 -0
  7. package/lib/components/SDropdownSectionFilter.vue +190 -0
  8. package/lib/components/SDropdownSectionFilterItem.vue +21 -0
  9. package/lib/components/SDropdownSectionFilterItemAvatar.vue +31 -0
  10. package/lib/components/SDropdownSectionFilterItemText.vue +20 -0
  11. package/lib/components/SDropdownSectionMenu.vue +39 -0
  12. package/lib/components/SIcon.vue +13 -0
  13. package/lib/components/SInputBase.vue +31 -31
  14. package/lib/components/SInputCheckbox.vue +4 -3
  15. package/lib/components/SInputCheckboxes.vue +74 -0
  16. package/lib/components/SInputDate.vue +182 -0
  17. package/lib/components/SInputDropdown.vue +159 -157
  18. package/lib/components/SInputDropdownItem.vue +46 -48
  19. package/lib/components/SInputDropdownItemAvatar.vue +99 -0
  20. package/lib/components/SInputDropdownItemText.vue +79 -16
  21. package/lib/components/SInputFile.vue +56 -60
  22. package/lib/components/SInputHMS.vue +120 -110
  23. package/lib/components/SInputNumber.vue +38 -9
  24. package/lib/components/SInputRadio.vue +39 -36
  25. package/lib/components/SInputRadios.vue +40 -53
  26. package/lib/components/SInputSelect.vue +7 -6
  27. package/lib/components/SInputSwitch.vue +193 -0
  28. package/lib/components/SInputSwitches.vue +88 -0
  29. package/lib/components/SInputText.vue +207 -62
  30. package/lib/components/SInputTextarea.vue +46 -32
  31. package/lib/components/SInputYMD.vue +123 -126
  32. package/lib/components/SMarkdown.vue +52 -0
  33. package/lib/components/SModal.vue +33 -57
  34. package/lib/components/SMount.vue +19 -0
  35. package/lib/components/SSheet.vue +50 -55
  36. package/lib/components/SSheetFooter.vue +1 -1
  37. package/lib/components/SSheetFooterAction.vue +24 -17
  38. package/lib/components/SSheetFooterActions.vue +1 -4
  39. package/lib/components/SSheetForm.vue +15 -0
  40. package/lib/components/SSheetMedium.vue +8 -10
  41. package/lib/components/SSheetTitle.vue +7 -14
  42. package/lib/components/SSnackbar.vue +58 -47
  43. package/lib/components/{SPortalSnackbars.vue → SSnackbars.vue} +17 -20
  44. package/lib/components/{icons/SIconPreloader.vue → SSpinner.vue} +5 -4
  45. package/lib/components/SStep.vue +107 -0
  46. package/lib/components/SSteps.vue +59 -0
  47. package/lib/components/STable.vue +242 -0
  48. package/lib/components/STableCell.vue +82 -0
  49. package/lib/components/STableCellAvatar.vue +69 -0
  50. package/lib/components/STableCellAvatars.vue +93 -0
  51. package/lib/components/STableCellDay.vue +40 -0
  52. package/lib/components/STableCellPill.vue +84 -0
  53. package/lib/components/STableCellText.vue +103 -0
  54. package/lib/components/STableColumn.vue +255 -0
  55. package/lib/components/STableFooter.vue +115 -0
  56. package/lib/components/STableHeader.vue +74 -0
  57. package/lib/components/STableItem.vue +38 -0
  58. package/lib/components/STooltip.vue +112 -0
  59. package/lib/composables/Dropdown.ts +40 -99
  60. package/lib/composables/Form.ts +21 -18
  61. package/lib/composables/Grid.ts +117 -0
  62. package/lib/composables/Markdown.ts +138 -0
  63. package/lib/composables/Step.ts +7 -0
  64. package/lib/composables/Table.ts +103 -0
  65. package/lib/composables/Tooltip.ts +91 -0
  66. package/lib/composables/Validation.ts +5 -9
  67. package/lib/composables/markdown/LinkPlugin.ts +45 -0
  68. package/lib/mixins/Sheet.ts +5 -3
  69. package/lib/stores/Snackbars.ts +48 -0
  70. package/lib/{assets/styles → styles}/base.css +0 -0
  71. package/lib/{assets/styles → styles}/bootstrap.css +1 -0
  72. package/lib/{assets/styles → styles}/variables.css +55 -48
  73. package/lib/support/Day.ts +8 -0
  74. package/lib/support/Num.ts +3 -0
  75. package/lib/support/Time.ts +5 -2
  76. package/lib/support/Utils.ts +4 -3
  77. package/lib/types/shims.d.ts +3 -0
  78. package/lib/validation/validators/requiredYmd.ts +1 -1
  79. package/lib/validation/validators/ymd.ts +4 -4
  80. package/package.json +59 -37
  81. package/CHANGELOG.md +0 -47
  82. package/lib/.DS_Store +0 -0
  83. package/lib/components/.DS_Store +0 -0
  84. package/lib/components/SDialog.vue +0 -140
  85. package/lib/components/SDropdownItem.vue +0 -78
  86. package/lib/components/SDropdownItemText.vue +0 -22
  87. package/lib/components/SDropdownItemUser.vue +0 -40
  88. package/lib/components/SInputDropdownItemTextTag.vue +0 -94
  89. package/lib/components/SInputDropdownItemUser.vue +0 -41
  90. package/lib/components/SInputDropdownItemUserTag.vue +0 -100
  91. package/lib/components/SPortalModals.vue +0 -74
  92. package/lib/components/icons/.DS_Store +0 -0
  93. package/lib/components/icons/SIconActivity.vue +0 -5
  94. package/lib/components/icons/SIconArrowDown.vue +0 -5
  95. package/lib/components/icons/SIconArrowLeft.vue +0 -5
  96. package/lib/components/icons/SIconArrowRight.vue +0 -5
  97. package/lib/components/icons/SIconArrowUp.vue +0 -5
  98. package/lib/components/icons/SIconBarChart.vue +0 -7
  99. package/lib/components/icons/SIconBriefcase.vue +0 -5
  100. package/lib/components/icons/SIconBuilding.vue +0 -5
  101. package/lib/components/icons/SIconCalendar.vue +0 -5
  102. package/lib/components/icons/SIconCheck.vue +0 -5
  103. package/lib/components/icons/SIconCheckCircle.vue +0 -6
  104. package/lib/components/icons/SIconCheckCircleThin.vue +0 -6
  105. package/lib/components/icons/SIconCheckSquare.vue +0 -6
  106. package/lib/components/icons/SIconChevronDown.vue +0 -5
  107. package/lib/components/icons/SIconChevronLeft.vue +0 -5
  108. package/lib/components/icons/SIconChevronRight.vue +0 -5
  109. package/lib/components/icons/SIconChevronUp.vue +0 -5
  110. package/lib/components/icons/SIconClock.vue +0 -6
  111. package/lib/components/icons/SIconCode.vue +0 -6
  112. package/lib/components/icons/SIconDatabase.vue +0 -5
  113. package/lib/components/icons/SIconDollarSign.vue +0 -5
  114. package/lib/components/icons/SIconDownload.vue +0 -6
  115. package/lib/components/icons/SIconDownloadCloud.vue +0 -6
  116. package/lib/components/icons/SIconEdit.vue +0 -6
  117. package/lib/components/icons/SIconEdit2.vue +0 -5
  118. package/lib/components/icons/SIconEdit3.vue +0 -6
  119. package/lib/components/icons/SIconEdit3Off.vue +0 -6
  120. package/lib/components/icons/SIconExternalLink.vue +0 -6
  121. package/lib/components/icons/SIconEye.vue +0 -6
  122. package/lib/components/icons/SIconFile.vue +0 -5
  123. package/lib/components/icons/SIconFilePlus.vue +0 -6
  124. package/lib/components/icons/SIconFileText.vue +0 -8
  125. package/lib/components/icons/SIconFlag.vue +0 -5
  126. package/lib/components/icons/SIconGitBranch.vue +0 -5
  127. package/lib/components/icons/SIconGitCommit.vue +0 -5
  128. package/lib/components/icons/SIconGitPullRequest.vue +0 -6
  129. package/lib/components/icons/SIconGlobe.vue +0 -5
  130. package/lib/components/icons/SIconGrab.vue +0 -10
  131. package/lib/components/icons/SIconGrid.vue +0 -8
  132. package/lib/components/icons/SIconHome.vue +0 -5
  133. package/lib/components/icons/SIconImage.vue +0 -6
  134. package/lib/components/icons/SIconInbox.vue +0 -5
  135. package/lib/components/icons/SIconInfo.vue +0 -7
  136. package/lib/components/icons/SIconLayout.vue +0 -5
  137. package/lib/components/icons/SIconList.vue +0 -10
  138. package/lib/components/icons/SIconLock.vue +0 -5
  139. package/lib/components/icons/SIconLogout.vue +0 -6
  140. package/lib/components/icons/SIconMail.vue +0 -6
  141. package/lib/components/icons/SIconMapPin.vue +0 -6
  142. package/lib/components/icons/SIconMoon.vue +0 -5
  143. package/lib/components/icons/SIconMoreHorizontal.vue +0 -7
  144. package/lib/components/icons/SIconMoreVertical.vue +0 -7
  145. package/lib/components/icons/SIconPauseFill.vue +0 -6
  146. package/lib/components/icons/SIconPlayCircle.vue +0 -6
  147. package/lib/components/icons/SIconPlayFill.vue +0 -5
  148. package/lib/components/icons/SIconPlus.vue +0 -5
  149. package/lib/components/icons/SIconPlusCircle.vue +0 -8
  150. package/lib/components/icons/SIconPlusOff.vue +0 -7
  151. package/lib/components/icons/SIconPreloaderDark.vue +0 -52
  152. package/lib/components/icons/SIconPreloaderLight.vue +0 -52
  153. package/lib/components/icons/SIconProgress.vue +0 -5
  154. package/lib/components/icons/SIconRadio.vue +0 -6
  155. package/lib/components/icons/SIconSave.vue +0 -5
  156. package/lib/components/icons/SIconSearch.vue +0 -5
  157. package/lib/components/icons/SIconSend.vue +0 -5
  158. package/lib/components/icons/SIconSettings.vue +0 -6
  159. package/lib/components/icons/SIconShare2.vue +0 -5
  160. package/lib/components/icons/SIconSkipBackFill.vue +0 -6
  161. package/lib/components/icons/SIconSliders.vue +0 -12
  162. package/lib/components/icons/SIconSun.vue +0 -13
  163. package/lib/components/icons/SIconTelescope.vue +0 -5
  164. package/lib/components/icons/SIconTrash.vue +0 -5
  165. package/lib/components/icons/SIconTrash2.vue +0 -7
  166. package/lib/components/icons/SIconTrash2Off.vue +0 -6
  167. package/lib/components/icons/SIconTrello.vue +0 -7
  168. package/lib/components/icons/SIconUser.vue +0 -6
  169. package/lib/components/icons/SIconUsers.vue +0 -8
  170. package/lib/components/icons/SIconWarning.vue +0 -7
  171. package/lib/components/icons/SIconX.vue +0 -5
  172. package/lib/components/icons/SIconXCircle.vue +0 -6
  173. package/lib/components/icons/SIconXCircleThin.vue +0 -6
  174. package/lib/components/icons/SIconXSquare.vue +0 -6
  175. package/lib/components/icons/SIconZap.vue +0 -5
  176. package/lib/composables/Dialog.ts +0 -38
  177. package/lib/composables/Modal.ts +0 -34
  178. package/lib/composables/Snackbar.ts +0 -18
  179. package/lib/store/Sefirot.ts +0 -17
  180. package/lib/store/dialog/index.ts +0 -42
  181. package/lib/store/modal/index.ts +0 -61
  182. package/lib/store/snackbars/index.ts +0 -70
@@ -0,0 +1,149 @@
1
+ <template>
2
+ <div class="SButtonGroup" :class="classes">
3
+ <button
4
+ v-for="item in items"
5
+ :key="item.value"
6
+ class="button"
7
+ :class="getButtonClasses(item)"
8
+ @click="handleClick(item.value)"
9
+ >
10
+ <span class="content">
11
+ {{ item.label }}
12
+ </span>
13
+ </button>
14
+ </div>
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ import { computed, PropType } from 'vue'
19
+
20
+ interface ButtonGroupItem {
21
+ label: string
22
+ value: string
23
+ mode: Mode
24
+ }
25
+
26
+ type Mode = 'neutral' | 'info' | 'success' | 'warning' | 'danger'
27
+ type Size = 'mini' | 'small' | 'medium' | 'large' | 'jumbo'
28
+
29
+
30
+ const props = defineProps({
31
+ items: { type: Array as PropType<ButtonGroupItem[]>, required: true },
32
+ size: { type: String as PropType<Size>, default: 'medium' },
33
+ modelValue: { type: String, default: null }
34
+ })
35
+
36
+ const emit = defineEmits(['update:modelValue'])
37
+
38
+ const classes = computed(() => [props.size])
39
+
40
+ function getButtonClasses(button: ButtonGroupItem) {
41
+ return [
42
+ { active: button.value === props.modelValue },
43
+ button.mode ?? 'neutral'
44
+ ]
45
+ }
46
+
47
+ function handleClick(value: string) {
48
+ emit('update:modelValue', value)
49
+ }
50
+ </script>
51
+
52
+ <style lang="postcss" scoped>
53
+ .SButtonGroup {
54
+ display: flex;
55
+ width: fit-content;
56
+ border: 1px solid var(--c-divider);
57
+ border-radius: 4px;
58
+ overflow: hidden;
59
+ }
60
+
61
+ .SButtonGroup.mini {
62
+ height: 28px;
63
+
64
+ .button {
65
+ padding: 0 8px;
66
+ height: 28px;
67
+ font-size: 12px;
68
+ font-weight: 500;
69
+ }
70
+ }
71
+
72
+ .SButtonGroup.small {
73
+ height: 32px;
74
+
75
+ .button {
76
+ padding: 0 10px;
77
+ height: 32px;
78
+ font-size: 12px;
79
+ font-weight: 500;
80
+ }
81
+ }
82
+
83
+ .SButtonGroup.medium {
84
+ height: 40px;
85
+
86
+ .button {
87
+ padding: 0 12px;
88
+ height: 40px;
89
+ font-size: 13px;
90
+ font-weight: 500;
91
+ }
92
+ }
93
+
94
+ .SButtonGroup.large {
95
+ height: 48px;
96
+
97
+ .button {
98
+ padding: 0 14px;
99
+ height: 48px;
100
+ font-size: 14px;
101
+ font-weight: 500;
102
+ }
103
+ }
104
+
105
+ .SButtonGroup.jumbo {
106
+ height: 64px;
107
+
108
+ .button {
109
+ padding: 0 24px;
110
+ height: 64px;
111
+ font-size: 14px;
112
+ font-weight: 500;
113
+ }
114
+ }
115
+
116
+ .button {
117
+ border-left: 1px solid transparent;
118
+ letter-spacing: .4px;
119
+ color: var(--c-text-2);
120
+ white-space: nowrap;
121
+ transition: color .25s, background-color .25s;
122
+
123
+ &:hover {
124
+ color: var(--c-text-1);
125
+ }
126
+ }
127
+
128
+ .button:not(:first-child) {
129
+ border-left: 1px solid var(--c-divider);
130
+ }
131
+
132
+ .button.active {
133
+ color: var(--c-text-dark-1);
134
+ }
135
+
136
+ .button.neutral.active { background-color: var(--c-black); }
137
+ .button.info.active { background-color: var(--c-info); }
138
+ .button.success.active { background-color: var(--c-success); }
139
+ .button.warning.active { background-color: var(--c-warning); }
140
+ .button.danger.active { background-color: var(--c-danger); }
141
+
142
+ .content {
143
+ display: flex;
144
+ justify-content: center;
145
+ align-items: center;
146
+ width: 100%;
147
+ height: 100%;
148
+ }
149
+ </style>
@@ -1,167 +1,43 @@
1
- <template>
2
- <div class="SDropdown">
3
- <div v-if="options.title" class="header">
4
- <p class="title">{{ options.title }}</p>
5
- <button class="close" @click="$emit('close')">
6
- <SIconX class="close-icon" />
7
- </button>
8
- </div>
1
+ <script setup lang="ts">
2
+ import { DropdownSection } from '../composables/Dropdown'
3
+ import SDropdownSection from './SDropdownSection.vue'
9
4
 
10
- <div v-if="options.search" class="search">
11
- <SInputText
12
- size="mini"
13
- mode="outlined"
14
- :placeholder="options.search.placeholder"
15
- :model-value="options.search.value.value"
16
- @update:model-value="v => options.search?.onInput(v)"
17
- />
18
- </div>
5
+ defineProps<{
6
+ sections: DropdownSection[]
7
+ }>()
8
+ </script>
19
9
 
10
+ <template>
11
+ <div class="SDropdown">
20
12
  <div class="container">
21
- <ul v-if="options.items.value.length > 0" class="list">
22
- <li
23
- v-for="(item, index) in options.items.value"
24
- :key="index"
25
- class="item"
26
- tabindex="0"
27
- @keydown.up.prevent
28
- @keydown.down.prevent
29
- @keyup.up.prevent="focusPrev"
30
- @keyup.down.prevent="focusNext"
31
- @keyup.enter="onClick(item)"
32
- @keyup.escape="$emit('close')"
33
- @click="onClick(item)"
34
- >
35
- <SDropdownItem :selected="options.selected" :item="item" />
36
- </li>
37
- </ul>
38
-
39
- <p v-else class="item-missing">{{ options.search?.missing }}</p>
13
+ <div v-for="(section, index) in sections" :key="index" class="section">
14
+ <SDropdownSection :section="section" />
15
+ </div>
40
16
  </div>
41
17
  </div>
42
18
  </template>
43
19
 
44
- <script setup lang="ts">
45
- import { PropType } from 'vue'
46
- import { Dropdown, Item } from '../composables/Dropdown'
47
- import SIconX from './icons/SIconX.vue'
48
- import SInputText from './SInputText.vue'
49
- import SDropdownItem from './SDropdownItem.vue'
50
-
51
- const props = defineProps({
52
- round: { type: Number, default: 8 },
53
- options: { type: Object as PropType<Dropdown>, required: true }
54
- })
55
-
56
- const emit = defineEmits(['close'])
57
-
58
- function focusFirstItem(): void {
59
- const el = document.querySelector('.SDropdown .item:first-child') as HTMLElement | null
60
- el?.focus?.()
61
- }
62
-
63
- function focusPrev(event: any): void {
64
- event.target.previousSibling?.focus()
65
- }
66
-
67
- function focusNext(event: any): void {
68
- event.target.nextSibling?.focus()
69
- }
70
-
71
- function onClick(item: Item): void {
72
- if (item.callback) {
73
- item.callback()
74
- } else if (props.options.callback) {
75
- props.options.callback(item)
76
- }
77
-
78
- if (props.options.closeOnClick) {
79
- emit('close')
80
- }
81
- }
82
- </script>
83
-
84
- <style lang="postcss" scoped>
20
+ <style scoped lang="postcss">
85
21
  .SDropdown {
86
- display: inline-block;
87
- overflow: hidden;
88
- min-width: 192px;
89
22
  border: 1px solid var(--c-divider-light);
90
- border-radius: 8px;
91
- background-color: var(--c-white);
92
- box-shadow: var(--shadow-depth-3);
93
- transition: background-color .25s;
94
- }
95
-
96
- .dark .SDropdown {
97
- background-color: var(--c-black-soft);
98
- }
99
-
100
- .header {
101
- display: flex;
102
- justify-content: space-between;
103
- border-bottom: 1px solid var(--c-divider-light);
104
- padding: 0 0 0 16px;
105
- }
106
-
107
- .title {
108
- padding: 4px 0 3px;
109
- font-size: 12px;
110
- font-weight: 500;
111
- color: var(--c-text-2);
112
- }
113
-
114
- .close {
115
- display: flex;
116
- justify-content: center;
117
- align-items: center;
118
- width: 32px;
119
- height: 31px;
120
- color: var(--c-text-2);
121
-
122
- &:hover {
123
- color: var(--c-text-1);
124
- }
125
- }
126
-
127
- .close-icon {
128
- width: 14px;
129
- height: 14px;
130
- fill: currentColor;
131
- }
132
-
133
- .search {
134
- padding: 8px 8px 7px;
135
- border-bottom: 1px solid var(--c-divider-light);
136
- }
137
-
138
- .container {
139
- max-height: 304px;
23
+ border-radius: 12px;
24
+ min-width: 256px;
25
+ max-height: 384px;
26
+ background-color: var(--c-bg);
140
27
  overflow-y: auto;
141
- }
142
28
 
143
- .item {
144
- &:hover,
145
- &:focus {
146
- background-color: var(--dropdown-item-hover-bg);
147
- outline: none;
29
+ &::-webkit-scrollbar {
30
+ display: none;
148
31
  }
149
- }
150
-
151
- .item:first-child {
152
- padding-top: 1px;
153
- }
154
32
 
155
- .item + .item {
156
- border-top: 1px solid var(--c-divider-light);
33
+ .dark & {
34
+ background-color: var(--c-bg-mute);
35
+ }
157
36
  }
158
37
 
159
- .item-missing {
160
- margin: 0;
161
- padding: 8px 16px;
162
- line-height: 16px;
163
- font-size: 12px;
164
- font-weight: 500;
165
- color: var(--c-text-2);
38
+ .section {
39
+ & + & {
40
+ border-top: 1px solid var(--c-divider-light);
41
+ }
166
42
  }
167
43
  </style>
@@ -0,0 +1,48 @@
1
+ <script setup lang="ts">
2
+ import { DropdownSection } from '../composables/Dropdown'
3
+ import SDropdownSectionFilter from './SDropdownSectionFilter.vue'
4
+ import SDropdownSectionMenu from './SDropdownSectionMenu.vue'
5
+
6
+ defineProps<{
7
+ section: DropdownSection
8
+ }>()
9
+ </script>
10
+
11
+ <template>
12
+ <SDropdownSectionMenu
13
+ v-if="section.type === 'menu'"
14
+ :options="section.options"
15
+ />
16
+ <SDropdownSectionFilter
17
+ v-else-if="section.type === 'filter'"
18
+ :search="section.search"
19
+ :selected="section.selected"
20
+ :options="section.options"
21
+ :on-click="section.onClick"
22
+ />
23
+ </template>
24
+
25
+ <style scoped lang="postcss">
26
+ .SDropdown {
27
+ border: 1px solid var(--c-divider-light);
28
+ border-radius: 12px;
29
+ min-width: 256px;
30
+ max-height: 384px;
31
+ background-color: var(--c-bg);
32
+ overflow-y: auto;
33
+
34
+ &::-webkit-scrollbar {
35
+ display: none;
36
+ }
37
+
38
+ .dark & {
39
+ background-color: var(--c-bg-mute);
40
+ }
41
+ }
42
+
43
+ .section {
44
+ & + & {
45
+ border-top: 1px solid var(--c-divider-light);
46
+ }
47
+ }
48
+ </style>
@@ -0,0 +1,190 @@
1
+ <script setup lang="ts">
2
+ import IconCheck from '@iconify-icons/ph/check'
3
+ import type { MaybeRef } from '@vueuse/core'
4
+ import Fuse from 'fuse.js'
5
+ import { ref, computed, unref, onMounted } from 'vue'
6
+ import { DropdownSectionFilterSelectedValue, DropdownSectionFilterOption } from '../composables/Dropdown'
7
+ import { isArray } from '../support/Utils'
8
+ import SDropdownSectionFilterItem from './SDropdownSectionFilterItem.vue'
9
+ import SIcon from './SIcon.vue'
10
+
11
+ const props = defineProps<{
12
+ search?: boolean
13
+ selected: MaybeRef<DropdownSectionFilterSelectedValue>
14
+ options: DropdownSectionFilterOption[]
15
+ onClick?(value: string | number | boolean): void
16
+ }>()
17
+
18
+ const input = ref<HTMLElement | null>(null)
19
+ const query = ref('')
20
+
21
+ const fuse = computed(() => {
22
+ return new Fuse(props.options, { keys: ['label'] })
23
+ })
24
+
25
+ const filteredOptions = computed(() => {
26
+ return !props.search || !query.value
27
+ ? props.options
28
+ : fuse.value.search(query.value).map((r) => r.item)
29
+ })
30
+
31
+ onMounted(() => {
32
+ input.value?.focus()
33
+ })
34
+
35
+ function isActive(value: string | number | boolean) {
36
+ const selected = unref(props.selected)
37
+
38
+ return isArray(selected)
39
+ ? selected.some((sv) => sv === value)
40
+ : selected === value
41
+ }
42
+
43
+ function focusPrev(event: any) {
44
+ event.target.parentNode.previousElementSibling?.firstElementChild?.focus()
45
+ }
46
+
47
+ function focusNext(event: any) {
48
+ event.target.parentNode.nextElementSibling?.firstElementChild?.focus()
49
+ }
50
+
51
+ function handleClick(option: DropdownSectionFilterOption, value: string | number | boolean) {
52
+ option.onClick && option.onClick(value)
53
+ props.onClick && props.onClick(value)
54
+ }
55
+ </script>
56
+
57
+ <template>
58
+ <div class="SDropdownSectionFilter">
59
+ <div v-if="search" class="search">
60
+ <input class="input" placeholder="Filter options" ref="input" v-model="query">
61
+ </div>
62
+
63
+ <ul v-if="filteredOptions.length" class="list">
64
+ <li v-for="option in filteredOptions" :key="option.label" class="item">
65
+ <button
66
+ class="button"
67
+ :class="{ active: isActive(option.value) }"
68
+ tabindex="0"
69
+ @keyup.up.prevent="focusPrev"
70
+ @keyup.down.prevent="focusNext"
71
+ @click="handleClick(option, option.value)"
72
+ >
73
+ <span class="checkbox">
74
+ <span class="checkbox-box">
75
+ <SIcon :icon="IconCheck" class="checkbox-icon" />
76
+ </span>
77
+ </span>
78
+ <span class="option-item">
79
+ <SDropdownSectionFilterItem :option="option" />
80
+ </span>
81
+ </button>
82
+ </li>
83
+ </ul>
84
+
85
+ <p v-else class="empty">
86
+ No options found.
87
+ </p>
88
+ </div>
89
+ </template>
90
+
91
+ <style scoped lang="postcss">
92
+ .search {
93
+ position: sticky;
94
+ top: 0;
95
+ border-bottom: 1px solid var(--c-divider-light);
96
+ padding: 8px;
97
+ background-color: var(--c-bg-elv-up);
98
+ }
99
+
100
+ .input {
101
+ border: 1px solid var(--c-divider);
102
+ border-radius: 6px;
103
+ padding: 0 8px;
104
+ width: 100%;
105
+ font-size: 12px;
106
+ line-height: 32px;
107
+ background-color: var(--c-bg);
108
+ transition: border-color 0.25s;
109
+
110
+ &::placeholder {
111
+ font-weight: 500;
112
+ color: var(--c-text-3);
113
+ }
114
+
115
+ &:hover { border-color: var(--c-black); }
116
+ &:focus { border-color: var(--c-info); }
117
+
118
+ .dark &:hover { border-color: var(--c-gray); }
119
+ .dark &:focus { border-color: var(--c-info); }
120
+ }
121
+
122
+ .list {
123
+ padding: 8px;
124
+ }
125
+
126
+ .button {
127
+ display: flex;
128
+ align-items: center;
129
+ border-radius: 6px;
130
+ padding: 0 8px;
131
+ width: 100%;
132
+ text-align: left;
133
+ transition: color 0.25s, background-color 0.25s;
134
+
135
+ &:hover,
136
+ &:focus {
137
+ background-color: var(--c-bg-mute);
138
+ }
139
+
140
+ .dark &:hover,
141
+ .dark &:focus {
142
+ background-color: var(--c-bg);
143
+ }
144
+ }
145
+
146
+ .checkbox {
147
+ display: block;
148
+ }
149
+
150
+ .checkbox-box {
151
+ display: flex;
152
+ justify-content: center;
153
+ align-items: center;
154
+ border: 1px solid var(--c-divider);
155
+ border-radius: 4px;
156
+ width: 16px;
157
+ height: 16px;
158
+ background-color: var(--c-bg);
159
+ transition: border-color 0.1s, background-color 0.1s;
160
+
161
+ .button.active & {
162
+ border-color: var(--c-info);
163
+ background-color: var(--c-info);
164
+ }
165
+ }
166
+
167
+ .checkbox-icon {
168
+ display: block;
169
+ width: 10px;
170
+ height: 10px;
171
+ color: var(--c-white);
172
+ opacity: 0;
173
+ transition: opacity 0.25s;
174
+
175
+ .button.active & {
176
+ opacity: 1;
177
+ }
178
+ }
179
+
180
+ .empty {
181
+ padding: 16px;
182
+ font-size: 12px;
183
+ font-weight: 500;
184
+ color: var(--c-text-2);
185
+
186
+ .search + & {
187
+ border-top: 1px solid var(--c-divider-light);
188
+ }
189
+ }
190
+ </style>
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ import { DropdownSectionFilterOption } from '../composables/Dropdown'
3
+ import SDropdownSectionFilterItemAvatar from './SDropdownSectionFilterItemAvatar.vue'
4
+ import SDropdownSectionFilterItemText from './SDropdownSectionFilterItemText.vue'
5
+
6
+ defineProps<{
7
+ option: DropdownSectionFilterOption
8
+ }>()
9
+ </script>
10
+
11
+ <template>
12
+ <SDropdownSectionFilterItemText
13
+ v-if="option.type === 'text' || option.type === undefined"
14
+ :label="option.label"
15
+ />
16
+ <SDropdownSectionFilterItemAvatar
17
+ v-else-if="option.type === 'avatar'"
18
+ :label="option.label"
19
+ :image="option.image"
20
+ />
21
+ </template>
@@ -0,0 +1,31 @@
1
+ <script setup lang="ts">
2
+ import SAvatar from './SAvatar.vue'
3
+
4
+ defineProps<{
5
+ label: string
6
+ image?: string | null
7
+ }>()
8
+ </script>
9
+
10
+ <template>
11
+ <span class="SDropdownSectionFilterItemAvatar">
12
+ <SAvatar size="nano" :avatar="image" :name="label" />
13
+ <span class="name">{{ label }}</span>
14
+ </span>
15
+ </template>
16
+
17
+ <style scoped lang="postcss">
18
+ .SDropdownSectionFilterItemAvatar {
19
+ display: inline-flex;
20
+ align-items: center;
21
+ padding-left: 8px;
22
+ min-height: 32px;
23
+ }
24
+
25
+ .name {
26
+ display: inline-block;
27
+ padding-left: 8px;
28
+ font-size: 12px;
29
+ font-weight: 500;
30
+ }
31
+ </style>
@@ -0,0 +1,20 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ label: string
4
+ }>()
5
+ </script>
6
+
7
+ <template>
8
+ <span class="SDropdownSectionFilterItemText">
9
+ {{ label }}
10
+ </span>
11
+ </template>
12
+
13
+ <style scoped lang="postcss">
14
+ .SDropdownSectionFilterItemText {
15
+ padding-left: 8px;
16
+ line-height: 32px;
17
+ font-size: 12px;
18
+ font-weight: 500;
19
+ }
20
+ </style>
@@ -0,0 +1,39 @@
1
+ <script setup lang="ts">
2
+ import { DropdownSectionMenuOption } from '../composables/Dropdown'
3
+
4
+ defineProps<{
5
+ options: DropdownSectionMenuOption[]
6
+ }>()
7
+ </script>
8
+
9
+ <template>
10
+ <ul class="SDropdownSectionMenu">
11
+ <li v-for="option in options" :key="option.label" class="item">
12
+ <button class="button" @click="option.onClick">
13
+ {{ option.label }}
14
+ </button>
15
+ </li>
16
+ </ul>
17
+ </template>
18
+
19
+ <style scoped lang="postcss">
20
+ .SDropdownSectionMenu {
21
+ padding: 8px;
22
+ }
23
+
24
+ .button {
25
+ display: block;
26
+ border-radius: 6px;
27
+ padding: 0 8px;
28
+ width: 100%;
29
+ text-align: left;
30
+ line-height: 32px;
31
+ font-size: 12px;
32
+ font-weight: 500;
33
+ transition: color 0.25s, background-color 0.25s;
34
+
35
+ &:hover {
36
+ background-color: var(--c-bg-elv-down);
37
+ }
38
+ }
39
+ </style>
@@ -0,0 +1,13 @@
1
+ <script setup lang="ts">
2
+ import { Icon, IconifyIcon } from '@iconify/vue/dist/offline'
3
+ import type { DefineComponent } from 'vue'
4
+
5
+ defineProps<{
6
+ icon: IconifyIcon | DefineComponent
7
+ }>()
8
+ </script>
9
+
10
+ <template>
11
+ <Icon v-if="typeof icon['body'] === 'string'" :icon="(icon as IconifyIcon)" />
12
+ <component v-else :is="icon" />
13
+ </template>