@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,199 @@
1
+ import { computed, defineComponent, nextTick, onBeforeUnmount, ref, watch, type PropType } from "vue";
2
+ import { ChevronDown } from "lucide-vue-next";
3
+ import HkPopover from "./HkPopover";
4
+ import HkScrollContainer from "./HkScrollContainer";
5
+ import "./HkPopupSelect.scss";
6
+
7
+ export interface HkPopupSelectOption {
8
+ value: string;
9
+ label: string;
10
+ }
11
+
12
+ const activePopupId = ref<string | null>(null);
13
+ let popupCounter = 0;
14
+
15
+ export function isAnyPopupOpen(): boolean {
16
+ return activePopupId.value !== null;
17
+ }
18
+
19
+ export function closeAllPopups(): void {
20
+ activePopupId.value = null;
21
+ }
22
+
23
+ function scrollToElement(selector: string): void {
24
+ const el = document.querySelector(selector) as HTMLElement | null;
25
+ el?.scrollIntoView({ block: "nearest" });
26
+ }
27
+
28
+ export default defineComponent({
29
+ name: "HkPopupSelect",
30
+ props: {
31
+ modelValue: { type: String, default: "" },
32
+ label: { type: String, default: undefined },
33
+ placeholder: { type: String, default: "" },
34
+ error: { type: String, default: undefined },
35
+ disabled: { type: Boolean, default: false },
36
+ required: { type: Boolean, default: false },
37
+ options: {
38
+ type: Array as PropType<HkPopupSelectOption[]>,
39
+ default: undefined,
40
+ },
41
+ },
42
+ emits: {
43
+ "update:modelValue": (_value: string) => true,
44
+ },
45
+ setup(props, { emit, slots }) {
46
+ const instanceId = `hk-popup-select-${++popupCounter}`;
47
+ const isOpen = ref(false);
48
+ const triggerRef = ref<HTMLElement>();
49
+ const highlightedIndex = ref(-1);
50
+
51
+ watch(isOpen, (open) => {
52
+ if (open) {
53
+ activePopupId.value = instanceId;
54
+ } else {
55
+ if (activePopupId.value === instanceId) {
56
+ activePopupId.value = null;
57
+ }
58
+ }
59
+ });
60
+
61
+ onBeforeUnmount(() => {
62
+ if (activePopupId.value === instanceId) {
63
+ activePopupId.value = null;
64
+ }
65
+ });
66
+
67
+ watch(activePopupId, (id) => {
68
+ if (id !== instanceId && isOpen.value) {
69
+ isOpen.value = false;
70
+ }
71
+ });
72
+
73
+ const normalizedOptions = computed<HkPopupSelectOption[]>(
74
+ () => props.options ?? [],
75
+ );
76
+
77
+ const displayLabel = computed(() => {
78
+ if (!props.modelValue) return props.placeholder || "";
79
+ const opt = normalizedOptions.value.find(
80
+ (o) => o.value === props.modelValue,
81
+ );
82
+ return opt?.label ?? props.modelValue;
83
+ });
84
+
85
+ function toggle() {
86
+ if (props.disabled) return;
87
+ isOpen.value = !isOpen.value;
88
+ }
89
+
90
+ function select(value: string) {
91
+ emit("update:modelValue", value);
92
+ isOpen.value = false;
93
+ }
94
+
95
+ function onTriggerKeydown(e: KeyboardEvent) {
96
+ if (props.disabled) return;
97
+ if (e.key === "Enter" || e.key === " ") {
98
+ e.preventDefault();
99
+ toggle();
100
+ } else if (e.key === "ArrowDown") {
101
+ e.preventDefault();
102
+ if (!isOpen.value) {
103
+ isOpen.value = true;
104
+ highlightedIndex.value = 0;
105
+ } else {
106
+ highlightedIndex.value = (highlightedIndex.value + 1) % normalizedOptions.value.length;
107
+ }
108
+ } else if (e.key === "ArrowUp") {
109
+ e.preventDefault();
110
+ if (!isOpen.value) {
111
+ isOpen.value = true;
112
+ } else {
113
+ highlightedIndex.value =
114
+ (highlightedIndex.value - 1 + normalizedOptions.value.length) %
115
+ normalizedOptions.value.length;
116
+ }
117
+ } else if (e.key === "Escape") {
118
+ isOpen.value = false;
119
+ }
120
+ }
121
+
122
+ watch(isOpen, (open) => {
123
+ if (open) {
124
+ const idx = normalizedOptions.value.findIndex(
125
+ (o) => o.value === props.modelValue,
126
+ );
127
+ highlightedIndex.value = idx;
128
+ nextTick(() => {
129
+ scrollToHighlighted();
130
+ });
131
+ } else {
132
+ highlightedIndex.value = -1;
133
+ }
134
+ });
135
+
136
+ function scrollToHighlighted() {
137
+ scrollToElement(`[data-select-index="${highlightedIndex.value}"]`);
138
+ }
139
+
140
+ watch(highlightedIndex, () => {
141
+ scrollToHighlighted();
142
+ });
143
+
144
+ return () => (
145
+ <div class="hk-popup-select-wrapper">
146
+ {props.label ? (
147
+ <label class="hk-popup-select-label">
148
+ {props.label}
149
+ {props.required && <span class="hk-popup-select-required">*</span>}
150
+ </label>
151
+ ) : null}
152
+ <button
153
+ ref={triggerRef}
154
+ type="button"
155
+ class="hk-popup-select-trigger"
156
+ data-error={props.error || undefined}
157
+ data-disabled={props.disabled || undefined}
158
+ disabled={props.disabled}
159
+ onClick={toggle}
160
+ onKeydown={onTriggerKeydown}
161
+ data-state={isOpen.value ? "open" : "closed"}
162
+ >
163
+ <span class="hk-popup-select-value">
164
+ {displayLabel.value || props.placeholder}
165
+ </span>
166
+ <ChevronDown size={16} class="hk-popup-select-arrow" />
167
+ </button>
168
+ <HkPopover
169
+ modelValue={isOpen.value}
170
+ onUpdate:modelValue={(v: boolean) => { isOpen.value = v; }}
171
+ anchorRef={triggerRef.value ?? null}
172
+ placement="bottom"
173
+ offset={4}
174
+ backdrop={false}
175
+ class="hk-popup-select-content"
176
+ >
177
+ <HkScrollContainer class="hk-popup-select-viewport">
178
+ {slots.default
179
+ ? slots.default()
180
+ : normalizedOptions.value.map((opt, i) => (
181
+ <div
182
+ key={opt.value}
183
+ class="hk-popup-select-item"
184
+ data-highlighted={highlightedIndex.value === i || undefined}
185
+ data-select-index={i}
186
+ data-checked={opt.value === props.modelValue || undefined}
187
+ onClick={() => select(opt.value)}
188
+ onPointerenter={() => { highlightedIndex.value = i; }}
189
+ >
190
+ <span>{opt.label}</span>
191
+ </div>
192
+ ))}
193
+ </HkScrollContainer>
194
+ </HkPopover>
195
+ {props.error ? <p class="hk-popup-select-error">{props.error}</p> : null}
196
+ </div>
197
+ );
198
+ },
199
+ });
@@ -0,0 +1,106 @@
1
+ .hk-progress-bar {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--space-4);
5
+ width: 100%;
6
+ }
7
+
8
+ .hk-progress-bar-label {
9
+ font-size: var(--text-2xs);
10
+ color: rgb(var(--color-muted));
11
+ line-height: 1;
12
+ }
13
+
14
+ .hk-progress-bar-track {
15
+ width: 100%;
16
+ height: 4px;
17
+ border-radius: var(--radius-full);
18
+ background: rgb(var(--color-muted) / 15%);
19
+ overflow: hidden;
20
+ position: relative;
21
+ }
22
+
23
+ .hk-progress-bar[data-size="xs"] .hk-progress-bar-track {
24
+ height: 2px;
25
+ }
26
+
27
+ .hk-progress-bar[data-size="md"] .hk-progress-bar-track {
28
+ height: 6px;
29
+ }
30
+
31
+ .hk-progress-bar-secondary {
32
+ position: absolute;
33
+ top: 0;
34
+ left: 0;
35
+ height: 100%;
36
+ border-radius: var(--radius-full);
37
+ transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
38
+ opacity: 0.3;
39
+
40
+ &-loading {
41
+ background: rgb(var(--color-primary));
42
+ }
43
+
44
+ &-done {
45
+ background: rgb(var(--color-success));
46
+ }
47
+
48
+ &-error {
49
+ background: rgb(var(--color-error));
50
+ }
51
+ }
52
+
53
+ .hk-progress-bar-fill {
54
+ position: relative;
55
+ height: 100%;
56
+ border-radius: var(--radius-full);
57
+ transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
58
+
59
+ &-loading {
60
+ background: rgb(var(--color-primary));
61
+ }
62
+
63
+ &-done {
64
+ background: rgb(var(--color-success));
65
+ }
66
+
67
+ &-error {
68
+ background: rgb(var(--color-error));
69
+ }
70
+ }
71
+
72
+ .hk-progress-bar-indeterminate {
73
+ height: 100%;
74
+ width: 40%;
75
+ border-radius: var(--radius-full);
76
+ position: absolute;
77
+ animation: hk-progress-indeterminate 1.4s ease-in-out infinite;
78
+
79
+ &-loading {
80
+ background: rgb(var(--color-primary));
81
+ }
82
+
83
+ &-done {
84
+ background: rgb(var(--color-success));
85
+ animation: none;
86
+ width: 100%;
87
+ }
88
+
89
+ &-error {
90
+ background: rgb(var(--color-error));
91
+ animation: none;
92
+ width: 100%;
93
+ }
94
+ }
95
+
96
+ @keyframes hk-progress-indeterminate {
97
+ 0% {
98
+ left: -40%;
99
+ }
100
+ 60% {
101
+ left: 100%;
102
+ }
103
+ 100% {
104
+ left: 100%;
105
+ }
106
+ }
@@ -0,0 +1,61 @@
1
+ import { computed, defineComponent, type PropType } from "vue";
2
+
3
+ import "./HkProgressBar.scss";
4
+
5
+ type ProgressBarStatus = "loading" | "done" | "error";
6
+
7
+ export default defineComponent({
8
+ name: "HkProgressBar",
9
+ props: {
10
+ value: { type: Number, default: null },
11
+ secondary: { type: Number, default: null },
12
+ max: { type: Number, default: 100 },
13
+ status: { type: String as PropType<ProgressBarStatus>, default: "loading" },
14
+ size: { type: String as PropType<"xs" | "sm" | "md">, default: "sm" },
15
+ showLabel: { type: Boolean, default: false },
16
+ label: { type: String, default: undefined },
17
+ },
18
+ setup(props) {
19
+ const pct = computed(() => {
20
+ if (props.value == null) return null;
21
+ return Math.min(100, Math.max(0, (props.value / props.max) * 100));
22
+ });
23
+
24
+ const secondaryPct = computed(() => {
25
+ if (props.secondary == null) return null;
26
+ return Math.min(100, Math.max(0, (props.secondary / props.max) * 100));
27
+ });
28
+
29
+ const displayLabel = computed(() => {
30
+ if (props.label) return props.label;
31
+ if (pct.value == null) return "";
32
+ return `${Math.round(pct.value)}%`;
33
+ });
34
+
35
+ return () => (
36
+ <div class="hk-progress-bar" data-size={props.size}>
37
+ {props.showLabel && (
38
+ <span class="hk-progress-bar-label">{displayLabel.value}</span>
39
+ )}
40
+ <div class="hk-progress-bar-track">
41
+ {pct.value != null ? (
42
+ <>
43
+ {secondaryPct.value != null && (
44
+ <div
45
+ class={["hk-progress-bar-secondary", `hk-progress-bar-secondary-${props.status}`]}
46
+ style={{ width: `${secondaryPct.value}%` }}
47
+ />
48
+ )}
49
+ <div
50
+ class={["hk-progress-bar-fill", `hk-progress-bar-fill-${props.status}`]}
51
+ style={{ width: `${pct.value}%` }}
52
+ />
53
+ </>
54
+ ) : (
55
+ <div class={["hk-progress-bar-indeterminate", `hk-progress-bar-indeterminate-${props.status}`]} />
56
+ )}
57
+ </div>
58
+ </div>
59
+ );
60
+ },
61
+ });
@@ -0,0 +1,67 @@
1
+ import { defineComponent, nextTick, ref, watch } from "vue";
2
+
3
+ import { useProgressDialog } from "../composables/useProgressDialog";
4
+ import HModal from "./HkModal";
5
+ import HProgressBar from "./HkProgressBar";
6
+ import HSpinner from "./HkSpinner";
7
+
8
+ export default defineComponent({
9
+ name: "HkProgressDialog",
10
+ setup() {
11
+ const state = useProgressDialog();
12
+ const logRef = ref<HTMLElement>();
13
+
14
+ watch(
15
+ () => state.logs.length,
16
+ () => {
17
+ nextTick(() => {
18
+ const el = logRef.value;
19
+ if (el) el.scrollTop = el.scrollHeight;
20
+ });
21
+ },
22
+ );
23
+
24
+ return () => (
25
+ <HModal
26
+ modelValue={state.open}
27
+ onUpdate:modelValue={() => {
28
+ /* non-closable */
29
+ }}
30
+ closable={false}
31
+ title={state.title}
32
+ width="30rem"
33
+ >
34
+ <div style={{ display: "flex", flexDirection: "column", gap: "0.75rem", padding: "0.25rem 0" }}>
35
+ {state.value !== null ? (
36
+ <HProgressBar value={state.value} max={state.max} showLabel size="sm" />
37
+ ) : (
38
+ <div style={{ display: "flex", justifyContent: "center", padding: "0.5rem 0" }}>
39
+ <HSpinner />
40
+ </div>
41
+ )}
42
+ {state.logs.length > 0 ? (
43
+ <div
44
+ ref={logRef}
45
+ style={{
46
+ maxHeight: "10rem",
47
+ overflow: "hidden auto",
48
+ borderRadius: "0.375rem",
49
+ background: "rgba(0, 0, 0, 0.06)",
50
+ padding: "0.5rem",
51
+ fontFamily: "monospace",
52
+ fontSize: "0.75rem",
53
+ lineHeight: "1.5",
54
+ }}
55
+ >
56
+ <ul style={{ listStyle: "none", margin: 0, padding: 0 }}>
57
+ {state.logs.map((line, i) => (
58
+ <li key={i}>{line}</li>
59
+ ))}
60
+ </ul>
61
+ </div>
62
+ ) : null}
63
+ </div>
64
+ </HModal>
65
+ );
66
+ },
67
+ });
@@ -0,0 +1,56 @@
1
+ .hk-progress-ring {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ flex-shrink: 0;
6
+ position: relative;
7
+ }
8
+
9
+ .hk-progress-ring-svg {
10
+ width: 100%;
11
+ height: 100%;
12
+ transform: rotate(0deg);
13
+ }
14
+
15
+ .hk-progress-ring-track {
16
+ stroke: var(--hk-progress-ring-track, var(--hi-color-border, rgba(0, 0, 0, 0.12)));
17
+ transition: stroke 0.3s ease;
18
+ }
19
+
20
+ .hk-progress-ring-fill {
21
+ stroke: var(--hi-color-primary, #58a6ff);
22
+ transition:
23
+ stroke-dashoffset 0.3s ease,
24
+ stroke 0.3s ease;
25
+ }
26
+
27
+ .hk-progress-ring-success .hk-progress-ring-fill {
28
+ stroke: var(--hi-color-success, #3fb950);
29
+ }
30
+
31
+ .hk-progress-ring-exception .hk-progress-ring-fill {
32
+ stroke: var(--hi-color-danger, #f85149);
33
+ }
34
+
35
+ .hk-progress-ring-label {
36
+ position: absolute;
37
+ inset: 0;
38
+ display: flex;
39
+ align-items: center;
40
+ justify-content: center;
41
+ font-weight: 600;
42
+ color: var(--hi-color-text-primary, #333);
43
+ user-select: none;
44
+ pointer-events: none;
45
+ }
46
+
47
+ .hk-progress-ring-overlay {
48
+ position: absolute;
49
+ inset: 0;
50
+ display: flex;
51
+ flex-direction: column;
52
+ align-items: center;
53
+ justify-content: center;
54
+ gap: 1px;
55
+ pointer-events: none;
56
+ }
@@ -0,0 +1,82 @@
1
+ import { computed, defineComponent, type PropType } from "vue";
2
+
3
+ import "./HkProgressRing.scss";
4
+
5
+ export default defineComponent({
6
+ name: "HkProgressRing",
7
+ props: {
8
+ value: { type: Number, default: 0 },
9
+ pct: { type: Number, default: undefined },
10
+ size: { type: Number, default: 120 },
11
+ strokeWidth: { type: Number, default: 8 },
12
+ variant: { type: String as PropType<"normal" | "success" | "exception">, default: "normal" },
13
+ showLabel: { type: Boolean, default: false },
14
+ /** Whether to render the background track circle. Set false for overlay-only use. */
15
+ showTrack: { type: Boolean, default: true },
16
+ },
17
+ setup(props, { slots }) {
18
+ const clampedValue = computed(() => Math.min(100, Math.max(0, props.pct ?? props.value)));
19
+
20
+ const radius = computed(() => (props.size - props.strokeWidth) / 2);
21
+ const circumference = computed(() => 2 * Math.PI * radius.value);
22
+ const dashOffset = computed(() => circumference.value * (1 - clampedValue.value / 100));
23
+
24
+ const variantClass = computed(() => {
25
+ if (props.variant === "success") return "hk-progress-ring-success";
26
+ if (props.variant === "exception") return "hk-progress-ring-exception";
27
+ return "";
28
+ });
29
+
30
+ const textSize = computed(() => Math.round(props.size / 5));
31
+
32
+ const center = computed(() => props.size / 2);
33
+
34
+ return () => (
35
+ <div
36
+ class={["hk-progress-ring", variantClass.value]}
37
+ style={{ width: `${props.size}px`, height: `${props.size}px` }}
38
+ role="progressbar"
39
+ aria-valuenow={clampedValue.value}
40
+ aria-valuemin={0}
41
+ aria-valuemax={100}
42
+ >
43
+ <svg class="hk-progress-ring-svg" viewBox={`0 0 ${props.size} ${props.size}`}>
44
+ {props.showTrack && (
45
+ <circle
46
+ class="hk-progress-ring-track"
47
+ cx={center.value}
48
+ cy={center.value}
49
+ r={radius.value}
50
+ fill="none"
51
+ stroke="currentColor"
52
+ stroke-width={props.strokeWidth}
53
+ stroke-linecap="round"
54
+ />
55
+ )}
56
+ <circle
57
+ class="hk-progress-ring-fill"
58
+ cx={center.value}
59
+ cy={center.value}
60
+ r={radius.value}
61
+ fill="none"
62
+ stroke-width={props.strokeWidth}
63
+ stroke-dasharray={circumference.value}
64
+ stroke-dashoffset={dashOffset.value}
65
+ stroke-linecap="round"
66
+ transform={`rotate(-90 ${center.value} ${center.value})`}
67
+ />
68
+ </svg>
69
+ {props.showLabel && !slots.default && (
70
+ <span class="hk-progress-ring-label" style={{ fontSize: `${textSize.value}px` }}>
71
+ {Math.round(clampedValue.value)}%
72
+ </span>
73
+ )}
74
+ {slots.default && (
75
+ <span class="hk-progress-ring-overlay">
76
+ {slots.default()}
77
+ </span>
78
+ )}
79
+ </div>
80
+ );
81
+ },
82
+ });
@@ -0,0 +1,119 @@
1
+ .hk-radio-group {
2
+ display: flex;
3
+ gap: 1rem;
4
+
5
+ &-horizontal {
6
+ flex-direction: row;
7
+ flex-wrap: wrap;
8
+ }
9
+
10
+ &-vertical {
11
+ flex-direction: column;
12
+ }
13
+
14
+ &-sm {
15
+ gap: 0.5rem;
16
+ }
17
+
18
+ &-lg {
19
+ gap: 1.25rem;
20
+ }
21
+ }
22
+
23
+ .hk-radio {
24
+ display: inline-flex;
25
+ align-items: center;
26
+ gap: 0.5rem;
27
+ cursor: pointer;
28
+ user-select: none;
29
+
30
+ &[data-disabled] {
31
+ opacity: 0.5;
32
+ cursor: not-allowed;
33
+ }
34
+ }
35
+
36
+ /* --- Box --- */
37
+
38
+ .hk-radio-box {
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ border-radius: 50%;
43
+ border: 1px solid var(--hi-color-border);
44
+ background: var(--hi-color-surface);
45
+ transition:
46
+ border-color 0.3s ease,
47
+ background 0.3s ease;
48
+ flex-shrink: 0;
49
+ position: relative;
50
+
51
+ [data-size="sm"] & {
52
+ width: 0.875rem;
53
+ height: 0.875rem;
54
+ }
55
+
56
+ [data-size="md"] & {
57
+ width: 1.125rem;
58
+ height: 1.125rem;
59
+ }
60
+
61
+ [data-size="lg"] & {
62
+ width: 1.375rem;
63
+ height: 1.375rem;
64
+ }
65
+
66
+ &[data-checked] {
67
+ background: var(--hi-color-primary);
68
+ border-color: var(--hi-color-primary);
69
+ }
70
+ }
71
+
72
+ .hk-radio:not([data-disabled]):hover .hk-radio-box:not([data-checked]) {
73
+ border-color: var(--hi-color-primary);
74
+ }
75
+
76
+ /* --- Hidden input --- */
77
+
78
+ .hk-radio-input {
79
+ position: absolute;
80
+ width: 0;
81
+ height: 0;
82
+ opacity: 0;
83
+ pointer-events: none;
84
+ }
85
+
86
+ /* --- Dot --- */
87
+
88
+ .hk-radio-dot {
89
+ width: 6px;
90
+ height: 6px;
91
+ border-radius: 50%;
92
+ background: var(--hi-color-text-on-primary, #fff);
93
+ z-index: 1;
94
+
95
+ [data-size="lg"] & {
96
+ width: 8px;
97
+ height: 8px;
98
+ }
99
+
100
+ [data-size="sm"] & {
101
+ width: 5px;
102
+ height: 5px;
103
+ }
104
+ }
105
+
106
+ /* --- Label --- */
107
+
108
+ .hk-radio-label-text {
109
+ font-size: 0.875rem;
110
+ color: var(--hi-color-text-primary);
111
+
112
+ [data-size="sm"] & {
113
+ font-size: 0.75rem;
114
+ }
115
+
116
+ [data-size="lg"] & {
117
+ font-size: 1rem;
118
+ }
119
+ }