@celestia-island/hikari 0.4.4

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 (187) hide show
  1. package/README.md +54 -0
  2. package/package.json +63 -0
  3. package/src/DemoApp.vue +400 -0
  4. package/src/components/HkActionBar.tsx +15 -0
  5. package/src/components/HkAlert.scss +121 -0
  6. package/src/components/HkAlert.tsx +85 -0
  7. package/src/components/HkAvatar.tsx +29 -0
  8. package/src/components/HkBadge.scss +117 -0
  9. package/src/components/HkBadge.tsx +50 -0
  10. package/src/components/HkBreadcrumb.tsx +39 -0
  11. package/src/components/HkButton.scss +128 -0
  12. package/src/components/HkButton.tsx +74 -0
  13. package/src/components/HkCard.scss +49 -0
  14. package/src/components/HkCard.tsx +38 -0
  15. package/src/components/HkCheckbox.scss +102 -0
  16. package/src/components/HkCheckbox.tsx +85 -0
  17. package/src/components/HkCollapse.tsx +48 -0
  18. package/src/components/HkColorPicker.scss +148 -0
  19. package/src/components/HkColorPicker.tsx +204 -0
  20. package/src/components/HkConfirmDialog.scss +12 -0
  21. package/src/components/HkConfirmDialog.tsx +75 -0
  22. package/src/components/HkDateTimePicker.scss +361 -0
  23. package/src/components/HkDateTimePicker.tsx +424 -0
  24. package/src/components/HkDivider.scss +85 -0
  25. package/src/components/HkDivider.tsx +51 -0
  26. package/src/components/HkDraggableGrid.scss +132 -0
  27. package/src/components/HkDraggableGrid.tsx +291 -0
  28. package/src/components/HkDraggableList.scss +110 -0
  29. package/src/components/HkDraggableList.tsx +238 -0
  30. package/src/components/HkDrawer.scss +175 -0
  31. package/src/components/HkDrawer.tsx +171 -0
  32. package/src/components/HkEmptyState.scss +42 -0
  33. package/src/components/HkEmptyState.tsx +41 -0
  34. package/src/components/HkErrorBoundary.scss +64 -0
  35. package/src/components/HkErrorBoundary.tsx +82 -0
  36. package/src/components/HkGaugeRing.scss +4 -0
  37. package/src/components/HkGaugeRing.tsx +130 -0
  38. package/src/components/HkHoverRevealAction.scss +66 -0
  39. package/src/components/HkHoverRevealAction.tsx +77 -0
  40. package/src/components/HkIcon.tsx +43 -0
  41. package/src/components/HkIconButton.tsx +42 -0
  42. package/src/components/HkImageViewer.scss +33 -0
  43. package/src/components/HkImageViewer.tsx +250 -0
  44. package/src/components/HkInput.scss +172 -0
  45. package/src/components/HkInput.tsx +175 -0
  46. package/src/components/HkKbd.tsx +26 -0
  47. package/src/components/HkKeywordSearchModal.scss +148 -0
  48. package/src/components/HkKeywordSearchModal.tsx +308 -0
  49. package/src/components/HkListTransition.scss +131 -0
  50. package/src/components/HkListTransition.tsx +29 -0
  51. package/src/components/HkLocalePickerPopup.tsx +42 -0
  52. package/src/components/HkLogo.scss +36 -0
  53. package/src/components/HkLogo.tsx +93 -0
  54. package/src/components/HkMarkdownRenderer.scss +141 -0
  55. package/src/components/HkMarkdownRenderer.tsx +123 -0
  56. package/src/components/HkMediaControlBar.scss +65 -0
  57. package/src/components/HkMediaControlBar.tsx +115 -0
  58. package/src/components/HkMediaPlayer.scss +29 -0
  59. package/src/components/HkMediaPlayer.tsx +210 -0
  60. package/src/components/HkMediaSlider.scss +54 -0
  61. package/src/components/HkMediaSlider.tsx +81 -0
  62. package/src/components/HkMediaVisualizer.scss +7 -0
  63. package/src/components/HkMediaVisualizer.tsx +119 -0
  64. package/src/components/HkMinimap.scss +76 -0
  65. package/src/components/HkMinimap.tsx +255 -0
  66. package/src/components/HkModal.scss +318 -0
  67. package/src/components/HkModal.tsx +481 -0
  68. package/src/components/HkModalBreadcrumb.scss +55 -0
  69. package/src/components/HkModalBreadcrumb.tsx +113 -0
  70. package/src/components/HkMorphingTabs.scss +141 -0
  71. package/src/components/HkMorphingTabs.tsx +113 -0
  72. package/src/components/HkNavItem.scss +81 -0
  73. package/src/components/HkNavItem.tsx +157 -0
  74. package/src/components/HkNumberInput.scss +154 -0
  75. package/src/components/HkNumberInput.tsx +161 -0
  76. package/src/components/HkPasswordInput.scss +289 -0
  77. package/src/components/HkPasswordInput.tsx +533 -0
  78. package/src/components/HkPhaseTransition.scss +25 -0
  79. package/src/components/HkPhaseTransition.tsx +40 -0
  80. package/src/components/HkPopover.scss +82 -0
  81. package/src/components/HkPopover.tsx +410 -0
  82. package/src/components/HkPopupSelect.scss +136 -0
  83. package/src/components/HkPopupSelect.tsx +199 -0
  84. package/src/components/HkProgressBar.scss +106 -0
  85. package/src/components/HkProgressBar.tsx +61 -0
  86. package/src/components/HkProgressDialog.tsx +67 -0
  87. package/src/components/HkProgressRing.scss +56 -0
  88. package/src/components/HkProgressRing.tsx +82 -0
  89. package/src/components/HkRadio.scss +119 -0
  90. package/src/components/HkRadio.tsx +75 -0
  91. package/src/components/HkRollingNumber.scss +37 -0
  92. package/src/components/HkRollingNumber.tsx +97 -0
  93. package/src/components/HkScrollContainer.scss +101 -0
  94. package/src/components/HkScrollContainer.tsx +362 -0
  95. package/src/components/HkSearchInput.scss +132 -0
  96. package/src/components/HkSearchInput.tsx +117 -0
  97. package/src/components/HkSelect.scss +143 -0
  98. package/src/components/HkSelect.tsx +230 -0
  99. package/src/components/HkSelectionGrid.scss +177 -0
  100. package/src/components/HkSelectionGrid.tsx +99 -0
  101. package/src/components/HkSelectionWaterfall.scss +93 -0
  102. package/src/components/HkSelectionWaterfall.tsx +90 -0
  103. package/src/components/HkSidebar.scss +81 -0
  104. package/src/components/HkSidebar.tsx +45 -0
  105. package/src/components/HkSkeleton.scss +44 -0
  106. package/src/components/HkSkeleton.tsx +30 -0
  107. package/src/components/HkSkeletonList.tsx +35 -0
  108. package/src/components/HkSpinner.scss +67 -0
  109. package/src/components/HkSpinner.tsx +41 -0
  110. package/src/components/HkSplash.scss +60 -0
  111. package/src/components/HkSplash.tsx +40 -0
  112. package/src/components/HkSwitch.scss +157 -0
  113. package/src/components/HkSwitch.tsx +72 -0
  114. package/src/components/HkTable.scss +198 -0
  115. package/src/components/HkTable.tsx +246 -0
  116. package/src/components/HkTabs.scss +109 -0
  117. package/src/components/HkTabs.tsx +111 -0
  118. package/src/components/HkTag.scss +88 -0
  119. package/src/components/HkTag.tsx +44 -0
  120. package/src/components/HkTextarea.scss +72 -0
  121. package/src/components/HkTextarea.tsx +104 -0
  122. package/src/components/HkTimeline.scss +139 -0
  123. package/src/components/HkTimeline.tsx +95 -0
  124. package/src/components/HkToast.scss +168 -0
  125. package/src/components/HkToast.tsx +175 -0
  126. package/src/components/HkTooltip.scss +47 -0
  127. package/src/components/HkTooltip.tsx +108 -0
  128. package/src/components/HkTree.scss +188 -0
  129. package/src/components/HkTree.tsx +217 -0
  130. package/src/components/HkTrendChart.tsx +180 -0
  131. package/src/components/HkWindowedItem.scss +17 -0
  132. package/src/components/HkWindowedItem.tsx +107 -0
  133. package/src/components/HkZoomToolbar.scss +35 -0
  134. package/src/components/HkZoomToolbar.tsx +69 -0
  135. package/src/composables/useBreakpoint.ts +41 -0
  136. package/src/composables/useClipboard.ts +37 -0
  137. package/src/composables/useConfirm.ts +35 -0
  138. package/src/composables/useProgressDialog.ts +52 -0
  139. package/src/composables/useReportedTransition.ts +69 -0
  140. package/src/composables/useScrollWindow.ts +21 -0
  141. package/src/composables/useToast.ts +2 -0
  142. package/src/demo.scss +24 -0
  143. package/src/demo.ts +22 -0
  144. package/src/env.d.ts +6 -0
  145. package/src/i18n/context.ts +89 -0
  146. package/src/i18n/locales/ar/components.json +43 -0
  147. package/src/i18n/locales/ar/components.toml +40 -0
  148. package/src/i18n/locales/de/components.json +43 -0
  149. package/src/i18n/locales/de/components.toml +40 -0
  150. package/src/i18n/locales/en/components.json +43 -0
  151. package/src/i18n/locales/en/components.toml +40 -0
  152. package/src/i18n/locales/es/components.json +43 -0
  153. package/src/i18n/locales/es/components.toml +40 -0
  154. package/src/i18n/locales/fr/components.json +43 -0
  155. package/src/i18n/locales/fr/components.toml +40 -0
  156. package/src/i18n/locales/ja/components.json +43 -0
  157. package/src/i18n/locales/ja/components.toml +40 -0
  158. package/src/i18n/locales/ko/components.json +43 -0
  159. package/src/i18n/locales/ko/components.toml +40 -0
  160. package/src/i18n/locales/pt/components.json +43 -0
  161. package/src/i18n/locales/pt/components.toml +40 -0
  162. package/src/i18n/locales/ru/components.json +43 -0
  163. package/src/i18n/locales/ru/components.toml +40 -0
  164. package/src/i18n/locales/zh-Hans/components.json +43 -0
  165. package/src/i18n/locales/zh-Hans/components.toml +40 -0
  166. package/src/i18n/locales/zh-Hant/components.json +43 -0
  167. package/src/i18n/locales/zh-Hant/components.toml +40 -0
  168. package/src/index.ts +137 -0
  169. package/src/render.ts +18 -0
  170. package/src/runtime/animationBus.ts +278 -0
  171. package/src/runtime/cronBus.ts +25 -0
  172. package/src/runtime/index.ts +12 -0
  173. package/src/runtime/useAsyncData.ts +30 -0
  174. package/src/runtime/useBreakpoint.ts +62 -0
  175. package/src/runtime/useClipboard.ts +37 -0
  176. package/src/runtime/useConfirm.ts +35 -0
  177. package/src/runtime/useOverlay.ts +82 -0
  178. package/src/runtime/usePopupManager.ts +89 -0
  179. package/src/runtime/useToast.ts +140 -0
  180. package/src/styles/index.scss +10 -0
  181. package/src/theme/index.ts +5 -0
  182. package/src/theme/presets.ts +307 -0
  183. package/src/theme/theme.scss +17 -0
  184. package/src/theme/useBackgroundLuminance.ts +145 -0
  185. package/src/theme/useSolarTime.ts +101 -0
  186. package/src/theme/useTheme.ts +141 -0
  187. package/src/tokens.scss +26 -0
@@ -0,0 +1,246 @@
1
+ import { computed, defineComponent, ref, type PropType } from "vue";
2
+
3
+ import { useI18n } from "../i18n/context";
4
+ import "./HkTable.scss";
5
+
6
+ interface Column {
7
+ key: string;
8
+ title: string;
9
+ width?: string;
10
+ sortable?: boolean;
11
+ align?: "left" | "center" | "right";
12
+ }
13
+
14
+ export default defineComponent({
15
+ name: "HkTable",
16
+ props: {
17
+ columns: { type: Array as PropType<Column[]>, required: true },
18
+ rows: { type: Array as PropType<Record<string, unknown>[]>, required: true },
19
+ rowKey: { type: String, default: undefined },
20
+ caption: { type: String, default: undefined },
21
+ size: { type: String as PropType<"sm" | "md" | "lg">, default: "md" },
22
+ bordered: { type: Boolean, default: false },
23
+ striped: { type: Boolean, default: false },
24
+ hover: { type: Boolean, default: false },
25
+ sortable: { type: Boolean, default: false },
26
+ selectable: { type: Boolean, default: false },
27
+ emptyText: { type: String, default: "" },
28
+ },
29
+ emits: {
30
+ "update:selectedRows": (_rows: Record<string, unknown>[]) => true,
31
+ },
32
+ setup(props, { emit, slots }) {
33
+ const { t } = useI18n();
34
+ const sortKey = ref<string | null>(null);
35
+ const sortDirection = ref<"asc" | "desc">("asc");
36
+ const selectedRowKeys = ref<Set<string>>(new Set());
37
+
38
+ function getRowKey(row: Record<string, unknown>, index: number): string {
39
+ if (props.rowKey && row[props.rowKey] != null) return String(row[props.rowKey]);
40
+ return String(index);
41
+ }
42
+
43
+ function toggleSort(key: string) {
44
+ if (sortKey.value === key) {
45
+ sortDirection.value = sortDirection.value === "asc" ? "desc" : "asc";
46
+ } else {
47
+ sortKey.value = key;
48
+ sortDirection.value = "asc";
49
+ }
50
+ }
51
+
52
+ const sortedRows = computed(() => {
53
+ if (!sortKey.value) return [...props.rows];
54
+ const dir = sortDirection.value === "asc" ? 1 : -1;
55
+ return [...props.rows].sort((a, b) => {
56
+ const aVal = a[sortKey.value!];
57
+ const bVal = b[sortKey.value!];
58
+ if (aVal == null && bVal == null) return 0;
59
+ if (aVal == null) return 1;
60
+ if (bVal == null) return -1;
61
+ if (aVal < bVal) return -1 * dir;
62
+ if (aVal > bVal) return 1 * dir;
63
+ return 0;
64
+ });
65
+ });
66
+
67
+ function toggleRow(row: Record<string, unknown>, index: number) {
68
+ const key = getRowKey(row, index);
69
+ const next = new Set(selectedRowKeys.value);
70
+ if (next.has(key)) next.delete(key);
71
+ else next.add(key);
72
+ selectedRowKeys.value = next;
73
+ emit(
74
+ "update:selectedRows",
75
+ sortedRows.value.filter((r, i) => next.has(getRowKey(r, i)))
76
+ );
77
+ }
78
+
79
+ function toggleAll() {
80
+ if (selectedRowKeys.value.size === sortedRows.value.length) {
81
+ selectedRowKeys.value = new Set();
82
+ emit("update:selectedRows", []);
83
+ } else {
84
+ const keys = new Set(sortedRows.value.map((r, i) => getRowKey(r, i)));
85
+ selectedRowKeys.value = keys;
86
+ emit("update:selectedRows", [...sortedRows.value]);
87
+ }
88
+ }
89
+
90
+ const allChecked = computed(
91
+ () =>
92
+ props.selectable &&
93
+ sortedRows.value.length > 0 &&
94
+ selectedRowKeys.value.size === sortedRows.value.length
95
+ );
96
+
97
+ const tableCls = computed(() => [
98
+ "hk-table",
99
+ `hk-table-${props.size}`,
100
+ props.bordered ? "hk-table-bordered" : "",
101
+ props.striped ? "hk-table-striped" : "",
102
+ props.hover ? "hk-table-hover" : "",
103
+ ]);
104
+
105
+ const totalCols = computed(() => props.columns.length + (props.selectable ? 1 : 0));
106
+
107
+ return () => (
108
+ <div class="hk-table-wrapper">
109
+ <table class={tableCls.value}>
110
+ {props.caption && <caption class="sr-only">{props.caption}</caption>}
111
+ <thead>
112
+ <tr class="hk-table-header-row">
113
+ {props.selectable && (
114
+ <th class="hk-table-header-cell" style={{ width: "40px" }}>
115
+ <label class="hk-table-checkbox-label">
116
+ <input
117
+ type="checkbox"
118
+ class="hk-table-checkbox-input"
119
+ checked={allChecked.value}
120
+ onChange={toggleAll}
121
+ />
122
+ <span
123
+ class={[
124
+ "hk-table-checkbox",
125
+ "hk-table-checkbox-md",
126
+ allChecked.value ? "hk-table-checkbox-checked" : "",
127
+ ]}
128
+ >
129
+ {allChecked.value && (
130
+ <svg
131
+ class="hk-table-checkbox-icon"
132
+ viewBox="0 0 24 24"
133
+ fill="none"
134
+ stroke="currentColor"
135
+ stroke-width="3"
136
+ stroke-linecap="round"
137
+ stroke-linejoin="round"
138
+ >
139
+ <polyline points="20 6 9 17 4 12" />
140
+ </svg>
141
+ )}
142
+ </span>
143
+ </label>
144
+ </th>
145
+ )}
146
+ {props.columns.map((col) => {
147
+ const canSort = props.sortable || col.sortable;
148
+ return (
149
+ <th
150
+ key={col.key}
151
+ class={[
152
+ "hk-table-header-cell",
153
+ col.align ? `hk-text-${col.align}` : "",
154
+ canSort ? "hk-table-header-sortable" : "",
155
+ canSort && sortKey.value === col.key ? "hk-table-header-sorted" : "",
156
+ ]}
157
+ style={{ width: col.width }}
158
+ onClick={() => canSort && toggleSort(col.key)}
159
+ >
160
+ <span class="hk-table-header-label">{col.title}</span>
161
+ {canSort && sortKey.value === col.key && (
162
+ <svg
163
+ class={[
164
+ "hk-table-sort-icon",
165
+ sortDirection.value === "desc" ? "hk-table-sort-icon-desc" : "",
166
+ ]}
167
+ viewBox="0 0 24 24"
168
+ fill="none"
169
+ stroke="currentColor"
170
+ stroke-width="2"
171
+ stroke-linecap="round"
172
+ stroke-linejoin="round"
173
+ >
174
+ <polyline points="18 15 12 9 6 15" />
175
+ </svg>
176
+ )}
177
+ </th>
178
+ );
179
+ })}
180
+ </tr>
181
+ </thead>
182
+ <tbody class="hk-table-body">
183
+ {sortedRows.value.length === 0 ? (
184
+ <tr>
185
+ <td colspan={totalCols.value} class="hk-table-empty">
186
+ {slots.empty ? slots.empty() : (props.emptyText || t("hk.table.noData", "No data"))}
187
+ </td>
188
+ </tr>
189
+ ) : (
190
+ sortedRows.value.map((row, index) => {
191
+ const rowKey = getRowKey(row, index);
192
+ return (
193
+ <tr key={rowKey} class="hk-table-row">
194
+ {props.selectable && (
195
+ <td class="hk-table-cell" style={{ width: "40px" }}>
196
+ <label class="hk-table-checkbox-label">
197
+ <input
198
+ type="checkbox"
199
+ class="hk-table-checkbox-input"
200
+ checked={selectedRowKeys.value.has(rowKey)}
201
+ onChange={() => toggleRow(row, index)}
202
+ />
203
+ <span
204
+ class={[
205
+ "hk-table-checkbox",
206
+ "hk-table-checkbox-md",
207
+ selectedRowKeys.value.has(rowKey) ? "hk-table-checkbox-checked" : "",
208
+ ]}
209
+ >
210
+ {selectedRowKeys.value.has(rowKey) && (
211
+ <svg
212
+ class="hk-table-checkbox-icon"
213
+ viewBox="0 0 24 24"
214
+ fill="none"
215
+ stroke="currentColor"
216
+ stroke-width="3"
217
+ stroke-linecap="round"
218
+ stroke-linejoin="round"
219
+ >
220
+ <polyline points="20 6 9 17 4 12" />
221
+ </svg>
222
+ )}
223
+ </span>
224
+ </label>
225
+ </td>
226
+ )}
227
+ {props.columns.map((col) => (
228
+ <td
229
+ key={col.key}
230
+ class={["hk-table-cell", col.align ? `hk-text-${col.align}` : ""]}
231
+ >
232
+ {slots[`cell-${col.key}`]
233
+ ? slots[`cell-${col.key}`]!({ row, value: row[col.key], index })
234
+ : row[col.key]}
235
+ </td>
236
+ ))}
237
+ </tr>
238
+ );
239
+ })
240
+ )}
241
+ </tbody>
242
+ </table>
243
+ </div>
244
+ );
245
+ },
246
+ });
@@ -0,0 +1,109 @@
1
+ /* HkTabs — unified underline / pill tab component */
2
+
3
+ .hk-tabs {
4
+ width: 100%;
5
+ }
6
+
7
+ /* === Shared list === */
8
+
9
+ .hk-tabs-list {
10
+ display: flex;
11
+ gap: var(--space-2);
12
+ }
13
+
14
+ .hk-tabs-list[data-variant="pill"] {
15
+ position: relative;
16
+ display: inline-flex;
17
+ gap: 0;
18
+ padding: 2px;
19
+ border-radius: var(--radius-sm);
20
+ border: 1px solid var(--border-faint);
21
+ background: rgb(var(--color-surface) / 60%);
22
+ }
23
+
24
+ /* === Shared trigger === */
25
+
26
+ .hk-tabs-trigger {
27
+ font-weight: 500;
28
+ color: rgb(var(--color-muted));
29
+ background: transparent;
30
+ border: none;
31
+ cursor: pointer;
32
+ font-family: inherit;
33
+ outline: none;
34
+ transition: color var(--duration-short) ease;
35
+
36
+ &:hover {
37
+ color: rgb(var(--color-text));
38
+ }
39
+
40
+ &[data-active] {
41
+ color: rgb(var(--color-primary));
42
+ }
43
+ }
44
+
45
+ /* === Underline variant === */
46
+
47
+ .hk-tabs[data-variant="underline"] .hk-tabs-list {
48
+ border-bottom: 1px solid rgb(var(--color-border) / 15%);
49
+ }
50
+
51
+ .hk-tabs[data-variant="underline"] .hk-tabs-trigger {
52
+ padding: var(--space-8) var(--space-16);
53
+ font-size: var(--text-base);
54
+ border-bottom: 2px solid transparent;
55
+ transition: background-color, border-color, color, box-shadow, opacity, transform, filter var(--duration-normal) ease;
56
+
57
+ &:hover {
58
+ background: var(--c-primary-faint);
59
+ }
60
+
61
+ &[data-active] {
62
+ border-bottom-color: rgb(var(--color-primary));
63
+ }
64
+ }
65
+
66
+ /* === Pill variant — mirrors SMorphingTabs exactly === */
67
+
68
+ .hk-tabs[data-variant="pill"] .hk-tabs-trigger {
69
+ position: relative;
70
+ z-index: 1;
71
+ display: flex;
72
+ align-items: center;
73
+ justify-content: center;
74
+ gap: var(--space-6);
75
+ flex: none;
76
+ padding: var(--space-4) var(--space-12);
77
+ font-size: var(--text-sm);
78
+ font-weight: 600;
79
+ border-radius: var(--radius-sm);
80
+ white-space: nowrap;
81
+ }
82
+
83
+ /* === Pill sliding indicator === */
84
+
85
+ .hk-tabs-indicator {
86
+ position: absolute;
87
+ top: 2px;
88
+ height: calc(100% - 4px);
89
+ border-radius: var(--radius-sm);
90
+ background: rgb(var(--color-primary) / 12%);
91
+ pointer-events: none;
92
+ z-index: 0;
93
+ transition: left var(--duration-normal) var(--ease-standard),
94
+ width var(--duration-normal) var(--ease-standard);
95
+ }
96
+
97
+ /* === Panel === */
98
+
99
+ .hk-tabs-panel {
100
+ outline: none;
101
+
102
+ &:not([data-active]) {
103
+ display: none;
104
+ }
105
+ }
106
+
107
+ .hk-tabs[data-variant="underline"] .hk-tabs-panel {
108
+ padding: var(--space-16) 0;
109
+ }
@@ -0,0 +1,111 @@
1
+ import { defineComponent, nextTick, onMounted, ref, watch, type ComponentPublicInstance, type PropType } from "vue";
2
+
3
+ import "./HkTabs.scss";
4
+
5
+ interface TabItem {
6
+ key: string;
7
+ label: string;
8
+ disabled?: boolean;
9
+ }
10
+
11
+ export default defineComponent({
12
+ name: "HkTabs",
13
+ props: {
14
+ modelValue: { type: String, required: true },
15
+ tabs: { type: Array as PropType<TabItem[]>, required: true },
16
+ variant: { type: String as PropType<"underline" | "pill">, default: "underline" },
17
+ renderPanels: { type: Boolean, default: true },
18
+ },
19
+ emits: {
20
+ "update:modelValue": (_value: string) => true,
21
+ },
22
+ setup(props, { emit, slots }) {
23
+ const triggerRefs = ref<Map<number, HTMLElement>>(new Map());
24
+ const indicatorStyle = ref<Record<string, string>>({});
25
+
26
+ function setTriggerRef(el: Element | ComponentPublicInstance | null, idx: number) {
27
+ if (el instanceof HTMLElement) {
28
+ triggerRefs.value.set(idx, el);
29
+ } else {
30
+ triggerRefs.value.delete(idx);
31
+ }
32
+ }
33
+
34
+ function updateIndicator() {
35
+ if (props.variant !== "pill") return;
36
+ const idx = props.tabs.findIndex((t) => t.key === props.modelValue);
37
+ const el = triggerRefs.value.get(idx >= 0 ? idx : 0);
38
+ if (!el) return;
39
+ const left = `${el.offsetLeft}px`;
40
+ const width = `${el.offsetWidth}px`;
41
+ if (indicatorStyle.value.left === left && indicatorStyle.value.width === width) return;
42
+ indicatorStyle.value = { left, width };
43
+ }
44
+
45
+ let resizeObserver: ResizeObserver | null = null;
46
+
47
+ onMounted(() => {
48
+ nextTick(updateIndicator);
49
+ if (props.variant === "pill") {
50
+ resizeObserver = new ResizeObserver(() => updateIndicator());
51
+ const firstEl = triggerRefs.value.get(0);
52
+ if (firstEl?.parentElement) {
53
+ resizeObserver.observe(firstEl.parentElement);
54
+ }
55
+ }
56
+ });
57
+
58
+ watch(() => props.modelValue, () => nextTick(updateIndicator));
59
+
60
+ watch(() => props.tabs, () => {
61
+ triggerRefs.value.clear();
62
+ nextTick(updateIndicator);
63
+ }, { deep: true });
64
+
65
+ return () => (
66
+ <div class="hk-tabs" data-variant={props.variant}>
67
+ <div
68
+ class="hk-tabs-list"
69
+ data-variant={props.variant === "pill" ? "pill" : undefined}
70
+ role="tablist"
71
+ >
72
+ {props.variant === "pill" && (
73
+ <div class="hk-tabs-indicator" style={indicatorStyle.value} />
74
+ )}
75
+ {props.tabs.map((tab, idx) => {
76
+ const active = tab.key === props.modelValue;
77
+ const disabled = tab.disabled;
78
+ return (
79
+ <button
80
+ key={tab.key}
81
+ ref={(el) => setTriggerRef(el, idx)}
82
+ type="button"
83
+ role="tab"
84
+ aria-selected={active}
85
+ aria-disabled={disabled || undefined}
86
+ class="hk-tabs-trigger"
87
+ data-active={active || undefined}
88
+ disabled={disabled}
89
+ onClick={() => emit("update:modelValue", tab.key)}
90
+ >
91
+ {slots[`icon-${tab.key}`]?.()}
92
+ {tab.label && <span>{tab.label}</span>}
93
+ </button>
94
+ );
95
+ })}
96
+ </div>
97
+
98
+ {props.renderPanels && props.tabs.map((tab) => (
99
+ <div
100
+ key={tab.key}
101
+ role="tabpanel"
102
+ class="hk-tabs-panel"
103
+ data-active={(tab.key === props.modelValue) || undefined}
104
+ >
105
+ {tab.key === props.modelValue && slots[tab.key]?.()}
106
+ </div>
107
+ ))}
108
+ </div>
109
+ );
110
+ },
111
+ });
@@ -0,0 +1,88 @@
1
+ .hk-tag {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ gap: var(--hi-space-4, 4px);
5
+ font-size: var(--hi-text-xs, 0.75rem);
6
+ font-weight: 500;
7
+ line-height: 1.4;
8
+ white-space: nowrap;
9
+ border: 1px solid transparent;
10
+ border-radius: var(--hi-radius-sm, 4px);
11
+ padding: var(--hi-space-2, 2px) var(--hi-space-8, 8px);
12
+ user-select: none;
13
+ }
14
+
15
+ .hk-tag-sm {
16
+ font-size: 0.625rem;
17
+ padding: 1px 6px;
18
+ }
19
+
20
+ .hk-tag-md {
21
+ font-size: var(--hi-text-xs, 0.75rem);
22
+ padding: var(--hi-space-2, 2px) var(--hi-space-8, 8px);
23
+ }
24
+
25
+ .hk-tag-icon {
26
+ display: inline-flex;
27
+ align-items: center;
28
+ flex-shrink: 0;
29
+ opacity: 0.7;
30
+ }
31
+
32
+ .hk-tag-close {
33
+ display: inline-flex;
34
+ align-items: center;
35
+ justify-content: center;
36
+ flex-shrink: 0;
37
+ width: 14px;
38
+ height: 14px;
39
+ border: none;
40
+ background: transparent;
41
+ color: inherit;
42
+ cursor: pointer;
43
+ opacity: 0.5;
44
+ padding: 0;
45
+ margin-left: var(--hi-space-2, 2px);
46
+ border-radius: var(--hi-radius-sm, 2px);
47
+ transition: opacity var(--hi-duration-fast, 0.15s) ease;
48
+ }
49
+
50
+ .hk-tag-close:hover {
51
+ opacity: 1;
52
+ }
53
+
54
+ .hk-tag-default {
55
+ background: color-mix(in srgb, var(--hi-color-surface) 60%, transparent);
56
+ color: var(--hi-color-text-primary);
57
+ border-color: var(--hi-color-border);
58
+ }
59
+
60
+ .hk-tag-primary {
61
+ background: color-mix(in srgb, var(--hi-color-primary) 10%, transparent);
62
+ color: var(--hi-color-primary);
63
+ border-color: color-mix(in srgb, var(--hi-color-primary) 20%, transparent);
64
+ }
65
+
66
+ .hk-tag-success {
67
+ background: color-mix(in srgb, var(--hi-color-success) 10%, transparent);
68
+ color: var(--hi-color-success);
69
+ border-color: color-mix(in srgb, var(--hi-color-success) 20%, transparent);
70
+ }
71
+
72
+ .hk-tag-warning {
73
+ background: color-mix(in srgb, var(--hi-color-warning) 10%, transparent);
74
+ color: var(--hi-color-warning);
75
+ border-color: color-mix(in srgb, var(--hi-color-warning) 20%, transparent);
76
+ }
77
+
78
+ .hk-tag-danger {
79
+ background: color-mix(in srgb, var(--hi-color-danger) 10%, transparent);
80
+ color: var(--hi-color-danger);
81
+ border-color: color-mix(in srgb, var(--hi-color-danger) 20%, transparent);
82
+ }
83
+
84
+ .hk-tag-info {
85
+ background: color-mix(in srgb, var(--hi-color-info) 10%, transparent);
86
+ color: var(--hi-color-info);
87
+ border-color: color-mix(in srgb, var(--hi-color-info) 20%, transparent);
88
+ }
@@ -0,0 +1,44 @@
1
+ import { computed, defineComponent, type PropType } from "vue";
2
+ import { X } from "lucide-vue-next";
3
+ import "./HkTag.scss";
4
+
5
+ export type TagVariant = "default" | "primary" | "success" | "warning" | "danger" | "info";
6
+
7
+ export default defineComponent({
8
+ name: "HkTag",
9
+ props: {
10
+ variant: { type: String as PropType<TagVariant>, default: "default" },
11
+ size: { type: String as PropType<"sm" | "md">, default: "md" },
12
+ closable: { type: Boolean, default: false },
13
+ },
14
+ emits: {
15
+ close: () => true,
16
+ },
17
+ setup(props, { emit, slots }) {
18
+ const cls = computed(() => [
19
+ "hk-tag",
20
+ `hk-tag-${props.variant}`,
21
+ `hk-tag-${props.size}`,
22
+ ]);
23
+
24
+ return () => (
25
+ <span class={cls.value}>
26
+ {slots.icon && (
27
+ <span class="hk-tag-icon">
28
+ {slots.icon()}
29
+ </span>
30
+ )}
31
+ {slots.default?.()}
32
+ {props.closable && (
33
+ <button
34
+ type="button"
35
+ class="hk-tag-close"
36
+ onClick={() => emit("close")}
37
+ >
38
+ <X size={12} />
39
+ </button>
40
+ )}
41
+ </span>
42
+ );
43
+ },
44
+ });
@@ -0,0 +1,72 @@
1
+ .hk-textarea {
2
+ display: block;
3
+ width: 100%;
4
+
5
+ &-disabled {
6
+ opacity: 0.5;
7
+ }
8
+
9
+ &-overlimit .hk-textarea-field {
10
+ border-color: var(--hi-color-danger);
11
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--hi-color-danger) 15%, transparent);
12
+ }
13
+ }
14
+
15
+ .hk-textarea-field {
16
+ display: block;
17
+ width: 100%;
18
+ min-height: 2.5rem;
19
+ padding: 0.5rem 0.75rem;
20
+ background: var(--hi-color-surface);
21
+ border: 1px solid var(--hi-color-border);
22
+ border-radius: var(--hi-radius-md, 6px);
23
+ color: var(--hi-color-text-primary);
24
+ font-family: inherit;
25
+ font-size: 0.875rem;
26
+ line-height: 1.5;
27
+ resize: vertical;
28
+ outline: none;
29
+ box-sizing: border-box;
30
+ transition:
31
+ background-color var(--hi-transition-base, 200ms cubic-bezier(0.4, 0, 0.2, 1)),
32
+ border-color var(--hi-transition-base, 200ms cubic-bezier(0.4, 0, 0.2, 1)),
33
+ box-shadow var(--hi-transition-base, 200ms cubic-bezier(0.4, 0, 0.2, 1));
34
+
35
+ &::placeholder {
36
+ color: var(--hi-color-muted);
37
+ opacity: 0.6;
38
+ }
39
+
40
+ &:hover:not(:disabled):not(.hk-textarea-disabled &) {
41
+ border-color: var(--hi-color-primary);
42
+ }
43
+
44
+ &:focus {
45
+ border-color: var(--hi-color-primary);
46
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--hi-color-primary) 12%, transparent);
47
+ }
48
+
49
+ &:disabled {
50
+ cursor: not-allowed;
51
+ opacity: 1;
52
+ }
53
+ }
54
+
55
+ .hk-textarea-field-autogrow {
56
+ resize: none;
57
+ overflow: hidden;
58
+ }
59
+
60
+ .hk-textarea-count {
61
+ display: flex;
62
+ justify-content: flex-end;
63
+ margin-top: 0.25rem;
64
+ font-size: var(--hi-text-xs, 0.75rem);
65
+ color: var(--hi-color-muted);
66
+ user-select: none;
67
+ }
68
+
69
+ .hk-textarea-count-over {
70
+ color: var(--hi-color-danger);
71
+ font-weight: 600;
72
+ }