@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,85 @@
1
+ import { computed, defineComponent, ref, type PropType } from "vue";
2
+ import { useI18n } from "../i18n/context";
3
+ import { AlertTriangle, CheckCircle, Info, X } from "lucide-vue-next";
4
+
5
+ import "./HkAlert.scss";
6
+
7
+ export type AlertVariant = "error" | "warning" | "info" | "success";
8
+ export type AlertSize = "sm" | "md" | "lg";
9
+
10
+ const VARIANT_ICONS: Record<AlertVariant, typeof AlertTriangle> = {
11
+ error: AlertTriangle,
12
+ warning: AlertTriangle,
13
+ info: Info,
14
+ success: CheckCircle,
15
+ };
16
+
17
+ export default defineComponent({
18
+ name: "HkAlert",
19
+ props: {
20
+ variant: { type: String as PropType<AlertVariant>, default: "error" },
21
+ message: { type: String, required: true },
22
+ title: { type: String, default: undefined },
23
+ closable: { type: Boolean, default: false },
24
+ banner: { type: Boolean, default: false },
25
+ size: { type: String as PropType<AlertSize>, default: "md" },
26
+ },
27
+ emits: {
28
+ close: () => true,
29
+ },
30
+ setup(props, { emit, slots }) {
31
+ const { t } = useI18n();
32
+ const isDismissed = ref(false);
33
+
34
+ const rootCls = computed(() => [
35
+ "hk-alert",
36
+ `hk-alert-${props.variant}`,
37
+ `hk-alert-${props.size}`,
38
+ props.banner ? "hk-alert-banner" : "",
39
+ !props.title ? "hk-alert-no-title" : "",
40
+ ]);
41
+
42
+ function onClose() {
43
+ isDismissed.value = true;
44
+ emit("close");
45
+ }
46
+
47
+ return () => {
48
+ if (isDismissed.value) return null;
49
+
50
+ const IconComp = VARIANT_ICONS[props.variant];
51
+
52
+ return (
53
+ <div class={rootCls.value}>
54
+ <span class="hk-alert-icon">
55
+ {slots.icon ? (
56
+ slots.icon()
57
+ ) : (
58
+ <IconComp size={18} />
59
+ )}
60
+ </span>
61
+
62
+ <div class="hk-alert-body">
63
+ {props.title && (
64
+ <p class="hk-alert-title">{props.title}</p>
65
+ )}
66
+ <p class="hk-alert-text">
67
+ {slots.default ? slots.default() : props.message}
68
+ </p>
69
+ </div>
70
+
71
+ {props.closable && (
72
+ <button
73
+ class="hk-alert-close"
74
+ type="button"
75
+ aria-label={t("hk.alert.close", "Close")}
76
+ onClick={onClose}
77
+ >
78
+ <X size={14} />
79
+ </button>
80
+ )}
81
+ </div>
82
+ );
83
+ };
84
+ },
85
+ });
@@ -0,0 +1,29 @@
1
+ import { computed, defineComponent, type PropType } from "vue";
2
+ import { useI18n } from "../i18n/context";
3
+ import "../../../components/src/styles/components/avatar.scss";
4
+
5
+ export default defineComponent({
6
+ name: "HkAvatar",
7
+ props: {
8
+ src: { type: String },
9
+ name: { type: String },
10
+ size: { type: String as PropType<"sm" | "md" | "lg">, default: "md" },
11
+ },
12
+ setup(props) {
13
+ const { t } = useI18n();
14
+ const cls = computed(() => [
15
+ "hk-avatar",
16
+ `hk-avatar-${props.size}`,
17
+ ]);
18
+
19
+ return () => (
20
+ <div class={cls.value}>
21
+ {props.src ? (
22
+ <img src={props.src} alt={props.name} />
23
+ ) : (
24
+ <span class="hk-avatar-fallback">{props.name?.charAt(0)?.toUpperCase() ?? t("hk.avatar.fallback", "?")}</span>
25
+ )}
26
+ </div>
27
+ );
28
+ },
29
+ });
@@ -0,0 +1,117 @@
1
+ /*
2
+ * HkBadge — canonical small label / tag / pill / status component.
3
+ *
4
+ * Color model is CSS-variable-driven so consumers can either pick a semantic
5
+ * variant class (sets all three channels coherently) or override any
6
+ * channel individually via the color / bgColor / borderColor props
7
+ * (rendered as inline --hk-badge-* custom properties).
8
+ */
9
+
10
+ .hk-badge {
11
+ --hk-badge-text: inherit;
12
+ --hk-badge-bg: transparent;
13
+ --hk-badge-border: transparent;
14
+
15
+ display: inline-flex;
16
+ align-items: center;
17
+ gap: var(--space-4);
18
+ font-size: var(--text-xs);
19
+ font-weight: 600;
20
+ padding: var(--space-2) var(--space-8);
21
+ border-radius: var(--radius-full);
22
+ border: 1px solid var(--hk-badge-border);
23
+ background: var(--hk-badge-bg);
24
+ color: var(--hk-badge-text);
25
+ line-height: 1.4;
26
+ white-space: nowrap;
27
+ }
28
+
29
+ /* ---------- size ---------- */
30
+ .hk-badge[data-size="sm"] {
31
+ font-size: var(--text-2xs);
32
+ padding: var(--space-1) var(--space-6);
33
+ }
34
+
35
+ .hk-badge[data-size="md"] {
36
+ font-size: var(--text-xs);
37
+ padding: var(--space-2) var(--space-8);
38
+ }
39
+
40
+ /* ---------- shape ---------- */
41
+ .hk-badge[data-pill="false"] {
42
+ border-radius: var(--radius-sm);
43
+ }
44
+
45
+ /* ---------- typography ---------- */
46
+ .hk-badge[data-mono] {
47
+ font-family: var(--font-mono);
48
+ font-weight: 600;
49
+ }
50
+
51
+ .hk-badge[data-uppercase] {
52
+ text-transform: uppercase;
53
+ letter-spacing: 0.04em;
54
+ }
55
+
56
+ /* ---------- dot indicator ---------- */
57
+ .hk-badge [data-el="dot"] {
58
+ width: 6px;
59
+ height: 6px;
60
+ border-radius: 50%;
61
+ background: var(--hk-badge-text);
62
+ flex-shrink: 0;
63
+ }
64
+
65
+ /* dot-only: strip the pill padding/border/background so it's just the
66
+ indicator dot, not a dot wrapped in a bordered pill. */
67
+ .hk-badge[data-dot-only] {
68
+ padding: 0;
69
+ border: none;
70
+ background: transparent;
71
+ }
72
+
73
+ /* ------
74
+ Semantic variants
75
+ ================================================================== */
76
+
77
+ .hk-badge-default {
78
+ --hk-badge-text: rgb(var(--color-text));
79
+ --hk-badge-bg: rgb(var(--color-surface) / var(--opacity-half));
80
+ --hk-badge-border: var(--border-input);
81
+ }
82
+
83
+ .hk-badge-primary {
84
+ --hk-badge-text: rgb(var(--color-primary));
85
+ --hk-badge-bg: var(--c-primary-dim);
86
+ --hk-badge-border: var(--c-primary-overlay);
87
+ }
88
+
89
+ .hk-badge-success {
90
+ --hk-badge-text: rgb(var(--color-success));
91
+ --hk-badge-bg: rgb(var(--color-success) / 10%);
92
+ --hk-badge-border: rgb(var(--color-success) / 20%);
93
+ }
94
+
95
+ .hk-badge-error {
96
+ --hk-badge-text: rgb(var(--color-error));
97
+ --hk-badge-bg: var(--c-error-dim);
98
+ --hk-badge-border: var(--c-error-overlay);
99
+ }
100
+
101
+ .hk-badge-warning {
102
+ --hk-badge-text: rgb(var(--color-warning));
103
+ --hk-badge-bg: rgb(var(--color-warning) / 10%);
104
+ --hk-badge-border: rgb(var(--color-warning) / 20%);
105
+ }
106
+
107
+ .hk-badge-info {
108
+ --hk-badge-text: rgb(var(--color-info));
109
+ --hk-badge-bg: rgb(var(--color-info) / 10%);
110
+ --hk-badge-border: rgb(var(--color-info) / 20%);
111
+ }
112
+
113
+ .hk-badge-muted {
114
+ --hk-badge-text: color-mix(in srgb, rgb(var(--color-muted)) 70%, rgb(var(--color-background)));
115
+ --hk-badge-bg: rgb(var(--color-muted) / 8%);
116
+ --hk-badge-border: rgb(var(--color-muted) / 12%);
117
+ }
@@ -0,0 +1,50 @@
1
+ import { computed, defineComponent, type CSSProperties, type PropType } from "vue";
2
+ import "./HkBadge.scss";
3
+
4
+ export type BadgeVariant =
5
+ | "default"
6
+ | "primary"
7
+ | "success"
8
+ | "error"
9
+ | "warning"
10
+ | "info"
11
+ | "muted";
12
+
13
+ export default defineComponent({
14
+ name: "HkBadge",
15
+ props: {
16
+ variant: { type: String as PropType<BadgeVariant>, default: "default" },
17
+ dot: { type: Boolean, default: false },
18
+ size: { type: String as PropType<"sm" | "md">, default: "md" },
19
+ mono: { type: Boolean, default: false },
20
+ uppercase: { type: Boolean, default: false },
21
+ pill: { type: Boolean, default: true },
22
+ color: { type: String, default: undefined },
23
+ bgColor: { type: String, default: undefined },
24
+ borderColor: { type: String, default: undefined },
25
+ },
26
+ setup(props, { slots }) {
27
+ const style = computed<CSSProperties>(() => {
28
+ const s: Record<string, string> = {};
29
+ if (props.color) s["--hk-badge-text"] = props.color;
30
+ if (props.bgColor) s["--hk-badge-bg"] = props.bgColor;
31
+ if (props.borderColor) s["--hk-badge-border"] = props.borderColor;
32
+ return s as CSSProperties;
33
+ });
34
+
35
+ return () => (
36
+ <span
37
+ class={["hk-badge", `hk-badge-${props.variant}`]}
38
+ data-size={props.size}
39
+ data-pill={props.pill ? undefined : "false"}
40
+ data-mono={props.mono ? "" : undefined}
41
+ data-uppercase={props.uppercase ? "" : undefined}
42
+ data-dot-only={(props.dot && !slots.default) || undefined}
43
+ style={Object.keys(style.value).length ? style.value : undefined}
44
+ >
45
+ {props.dot ? <span data-el="dot" /> : null}
46
+ {slots.default?.()}
47
+ </span>
48
+ );
49
+ },
50
+ });
@@ -0,0 +1,39 @@
1
+ import { computed, defineComponent, type PropType } from "vue";
2
+ import { useI18n } from "../i18n/context";
3
+ import "../../../components/src/styles/components/breadcrumb.scss";
4
+
5
+ export default defineComponent({
6
+ name: "HkBreadcrumb",
7
+ props: {
8
+ items: { type: Array as PropType<{ label: string; to?: string }[]>, required: true },
9
+ separator: { type: String },
10
+ size: { type: String as PropType<"sm" | "md" | "lg">, default: "md" },
11
+ },
12
+ setup(props) {
13
+ const { t } = useI18n();
14
+ const cls = computed(() => ["hk-breadcrumb", `hk-breadcrumb-${props.size}`]);
15
+
16
+ return () => (
17
+ <nav aria-label={t("hk.breadcrumb.label", "Breadcrumb")}>
18
+ <ol class={cls.value}>
19
+ {props.items.map((item, index) => (
20
+ <li key={index} class="hk-breadcrumb-item">
21
+ {item.to ? (
22
+ <a href={item.to} class="hk-breadcrumb-link">{item.label}</a>
23
+ ) : (
24
+ <span class="hk-breadcrumb-link">{item.label}</span>
25
+ )}
26
+ {index < props.items.length - 1 && (
27
+ <span class="hk-breadcrumb-separator">
28
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
29
+ <polyline points="9 18 15 12 9 6" />
30
+ </svg>
31
+ </span>
32
+ )}
33
+ </li>
34
+ ))}
35
+ </ol>
36
+ </nav>
37
+ );
38
+ },
39
+ });
@@ -0,0 +1,128 @@
1
+ @use "../tokens";
2
+
3
+ /* HkButton */
4
+ .hk-btn {
5
+ display: inline-flex;
6
+ align-items: center;
7
+ justify-content: center;
8
+ gap: var(--space-6);
9
+ font-family: inherit;
10
+ font-weight: 600;
11
+ line-height: 1;
12
+ border: 1px solid transparent;
13
+ border-radius: var(--radius-sm);
14
+ cursor: pointer;
15
+ transition: background-color, border-color, color, box-shadow, opacity, transform, filter var(--duration-normal) var(--ease-standard);
16
+ white-space: nowrap;
17
+ user-select: none;
18
+ outline: none;
19
+ position: relative;
20
+ min-height: 2.5rem;
21
+ }
22
+
23
+ .hk-btn:focus-visible {
24
+ box-shadow:
25
+ 0 0 0 2px rgb(var(--color-surface)),
26
+ 0 0 0 4px rgb(var(--color-primary));
27
+ }
28
+
29
+ .hk-btn:disabled {
30
+ opacity: 0.5;
31
+ cursor: not-allowed;
32
+ }
33
+
34
+ /* ---- Size variants ---- */
35
+
36
+ .hk-btn-sm {
37
+ padding: var(--space-6) var(--space-12);
38
+ font-size: var(--text-xs);
39
+ }
40
+
41
+ .hk-btn-md {
42
+ padding: var(--space-8) var(--space-16);
43
+ font-size: var(--text-base);
44
+ }
45
+
46
+ .hk-btn-lg {
47
+ padding: var(--space-10) var(--space-24);
48
+ font-size: var(--text-md);
49
+ }
50
+
51
+ /* ---- Primary ---- */
52
+
53
+ .hk-btn-primary {
54
+ background: rgb(var(--color-primary));
55
+ color: rgb(var(--color-on-solid));
56
+ }
57
+
58
+ .hk-btn-primary:hover:not(:disabled) {
59
+ filter: brightness(1.1);
60
+ box-shadow: var(--shadow-button);
61
+ }
62
+
63
+ .hk-btn-primary:active:not(:disabled) {
64
+ filter: brightness(0.95);
65
+ }
66
+
67
+ /* ---- Secondary ---- */
68
+
69
+ .hk-btn-secondary {
70
+ background: rgb(var(--color-surface));
71
+ color: rgb(var(--color-text));
72
+ border-color: rgb(var(--color-border) / 20%);
73
+ backdrop-filter: blur(var(--blur-sm));
74
+ }
75
+
76
+ .hk-btn-secondary:hover:not(:disabled) {
77
+ background: rgb(var(--color-surface));
78
+ border-color: var(--c-primary-strong);
79
+ }
80
+
81
+ /* ---- Outline ---- */
82
+
83
+ .hk-btn-outline {
84
+ background: transparent;
85
+ color: rgb(var(--color-primary));
86
+ border-color: var(--c-primary-intense);
87
+ }
88
+
89
+ .hk-btn-outline:hover:not(:disabled) {
90
+ background: var(--c-primary-light);
91
+ border-color: rgb(var(--color-primary));
92
+ }
93
+
94
+ /* ---- Ghost ---- */
95
+
96
+ .hk-btn-ghost {
97
+ background: transparent;
98
+ color: rgb(var(--color-text));
99
+ border-color: transparent;
100
+ }
101
+
102
+ .hk-btn-ghost:hover:not(:disabled) {
103
+ background: var(--c-primary-light);
104
+ }
105
+
106
+ /* ---- Danger ---- */
107
+
108
+ .hk-btn-danger {
109
+ background: rgb(var(--color-error));
110
+ color: rgb(var(--color-on-solid));
111
+ }
112
+
113
+ .hk-btn-danger:hover:not(:disabled) {
114
+ filter: brightness(1.1);
115
+ box-shadow: var(--shadow-button-danger);
116
+ }
117
+
118
+ /* ---- Block ---- */
119
+
120
+ .hk-btn-block {
121
+ width: 100%;
122
+ }
123
+
124
+ /* ---- Loading ---- */
125
+
126
+ .hk-btn-loading {
127
+ pointer-events: none;
128
+ }
@@ -0,0 +1,74 @@
1
+ import { computed, defineComponent, useAttrs, type PropType } from "vue";
2
+
3
+ import "./HkButton.scss";
4
+ import HIcon from "./HkIcon";
5
+ import HKbd from "./HkKbd";
6
+ import HSpinner from "./HkSpinner";
7
+
8
+ export type ButtonVariant =
9
+ | "primary"
10
+ | "secondary"
11
+ | "ghost"
12
+ | "danger"
13
+ | "outline";
14
+ export type ButtonSize = "sm" | "md" | "lg";
15
+
16
+ export default defineComponent({
17
+ name: "HkButton",
18
+ inheritAttrs: false,
19
+ props: {
20
+ variant: { type: String as PropType<ButtonVariant>, default: "primary" },
21
+ size: { type: String as PropType<ButtonSize>, default: "md" },
22
+ loading: { type: Boolean, default: false },
23
+ disabled: { type: Boolean, default: false },
24
+ block: { type: Boolean, default: false },
25
+ ariaLabel: { type: String, default: undefined },
26
+ shortcut: { type: String, default: undefined },
27
+ icon: { type: String, default: undefined },
28
+ suffix: { type: String, default: undefined },
29
+ },
30
+ emits: {
31
+ click: (_e: MouseEvent) => true,
32
+ },
33
+ setup(props, { emit, slots }) {
34
+ const attrs = useAttrs();
35
+
36
+ const buttonClass = computed(() => [
37
+ "hk-btn",
38
+ `hk-btn-${props.variant}`,
39
+ `hk-btn-${props.size}`,
40
+ props.block ? "hk-btn-block" : "",
41
+ props.loading ? "hk-btn-loading" : "",
42
+ props.shortcut ? "hk-btn-has-shortcut" : "",
43
+ ]);
44
+
45
+ return () => (
46
+ <button
47
+ {...attrs}
48
+ type={(attrs.type as "button" | "submit" | "reset") || "button"}
49
+ disabled={props.disabled || props.loading}
50
+ class={[buttonClass.value, attrs.class]}
51
+ style={attrs.style || undefined}
52
+ aria-label={props.ariaLabel}
53
+ aria-busy={props.loading || undefined}
54
+ onClick={(e) => emit("click", e)}
55
+ >
56
+ {props.loading ? <HSpinner size="xs" tone="current" /> : null}
57
+ {!props.loading && props.icon ? (
58
+ <span class="hk-btn-icon">
59
+ <HIcon name={props.icon} size={16} />
60
+ </span>
61
+ ) : null}
62
+ {slots.default?.()}
63
+ {props.suffix ? (
64
+ <span class="hk-btn-suffix">
65
+ <HIcon name={props.suffix} size={16} />
66
+ </span>
67
+ ) : null}
68
+ {props.shortcut ? (
69
+ <HKbd keys={props.shortcut!} size="sm" />
70
+ ) : null}
71
+ </button>
72
+ );
73
+ },
74
+ });
@@ -0,0 +1,49 @@
1
+ @use "../tokens";
2
+
3
+ /* HkCard — canonical surface card. Hover effect mirrors the node card:
4
+ only box-shadow and border-color animate. No transforms, so the card
5
+ never displaces its neighbors or triggers layout reflow. */
6
+ .hk-card {
7
+ display: block;
8
+ overflow: hidden;
9
+ background: rgb(var(--color-surface) / var(--opacity-half));
10
+ backdrop-filter: blur(var(--blur-md));
11
+ border: 1px solid var(--border-subtle);
12
+ border-radius: var(--radius-md);
13
+ box-shadow: var(--shadow-elevated);
14
+ }
15
+
16
+ .hk-card-hoverable {
17
+ cursor: pointer;
18
+ transition:
19
+ box-shadow var(--duration-normal) ease,
20
+ border-color var(--duration-normal) ease;
21
+ }
22
+
23
+ .hk-card-hoverable:hover {
24
+ box-shadow: var(--shadow-elevated);
25
+ border-color: var(--c-primary-intense);
26
+ }
27
+
28
+ .hk-card-header {
29
+ padding: var(--space-12) var(--space-16);
30
+ border-bottom: 1px solid var(--border-faint);
31
+ }
32
+
33
+ .hk-card-title {
34
+ font-size: var(--text-base);
35
+ font-weight: 600;
36
+ }
37
+
38
+ .hk-card-body {
39
+ padding: var(--space-16);
40
+ }
41
+
42
+ .hk-card-body-unpadded {
43
+ padding: 0;
44
+ }
45
+
46
+ .hk-card-footer {
47
+ padding: var(--space-12) var(--space-16);
48
+ border-top: 1px solid rgb(var(--color-border) / 10%);
49
+ }
@@ -0,0 +1,38 @@
1
+ import { defineComponent } from "vue";
2
+
3
+ import "./HkCard.scss";
4
+
5
+ export default defineComponent({
6
+ name: "HkCard",
7
+ props: {
8
+ title: { type: String, default: undefined },
9
+ hoverable: { type: Boolean, default: false },
10
+ padded: { type: Boolean, default: true },
11
+ class: { type: String, default: "" },
12
+ },
13
+ emits: {
14
+ click: (_e: MouseEvent) => true,
15
+ },
16
+ setup(props, { emit, slots }) {
17
+ return () => (
18
+ <div
19
+ class={[
20
+ "hk-card",
21
+ props.hoverable ? "hk-card-hoverable" : "",
22
+ props.class,
23
+ ]}
24
+ onClick={(e) => emit("click", e)}
25
+ >
26
+ {props.title || slots.header ? (
27
+ <div class="hk-card-header">
28
+ {slots.header ? slots.header() : <h3 class="hk-card-title">{props.title}</h3>}
29
+ </div>
30
+ ) : null}
31
+ <div class={["hk-card-body", !props.padded ? "hk-card-body-unpadded" : ""]}>
32
+ {slots.default?.()}
33
+ </div>
34
+ {slots.footer ? <div class="hk-card-footer">{slots.footer()}</div> : null}
35
+ </div>
36
+ );
37
+ },
38
+ });