@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.
- package/README.md +54 -0
- package/package.json +63 -0
- package/src/DemoApp.vue +400 -0
- package/src/components/HkActionBar.tsx +15 -0
- package/src/components/HkAlert.scss +121 -0
- package/src/components/HkAlert.tsx +85 -0
- package/src/components/HkAvatar.tsx +29 -0
- package/src/components/HkBadge.scss +117 -0
- package/src/components/HkBadge.tsx +50 -0
- package/src/components/HkBreadcrumb.tsx +39 -0
- package/src/components/HkButton.scss +128 -0
- package/src/components/HkButton.tsx +74 -0
- package/src/components/HkCard.scss +49 -0
- package/src/components/HkCard.tsx +38 -0
- package/src/components/HkCheckbox.scss +102 -0
- package/src/components/HkCheckbox.tsx +85 -0
- package/src/components/HkCollapse.tsx +48 -0
- package/src/components/HkColorPicker.scss +148 -0
- package/src/components/HkColorPicker.tsx +204 -0
- package/src/components/HkConfirmDialog.scss +12 -0
- package/src/components/HkConfirmDialog.tsx +75 -0
- package/src/components/HkDateTimePicker.scss +361 -0
- package/src/components/HkDateTimePicker.tsx +424 -0
- package/src/components/HkDivider.scss +85 -0
- package/src/components/HkDivider.tsx +51 -0
- package/src/components/HkDraggableGrid.scss +132 -0
- package/src/components/HkDraggableGrid.tsx +291 -0
- package/src/components/HkDraggableList.scss +110 -0
- package/src/components/HkDraggableList.tsx +238 -0
- package/src/components/HkDrawer.scss +175 -0
- package/src/components/HkDrawer.tsx +171 -0
- package/src/components/HkEmptyState.scss +42 -0
- package/src/components/HkEmptyState.tsx +41 -0
- package/src/components/HkErrorBoundary.scss +64 -0
- package/src/components/HkErrorBoundary.tsx +82 -0
- package/src/components/HkGaugeRing.scss +4 -0
- package/src/components/HkGaugeRing.tsx +130 -0
- package/src/components/HkHoverRevealAction.scss +66 -0
- package/src/components/HkHoverRevealAction.tsx +77 -0
- package/src/components/HkIcon.tsx +43 -0
- package/src/components/HkIconButton.tsx +42 -0
- package/src/components/HkImageViewer.scss +33 -0
- package/src/components/HkImageViewer.tsx +250 -0
- package/src/components/HkInput.scss +172 -0
- package/src/components/HkInput.tsx +175 -0
- package/src/components/HkKbd.tsx +26 -0
- package/src/components/HkKeywordSearchModal.scss +148 -0
- package/src/components/HkKeywordSearchModal.tsx +308 -0
- package/src/components/HkListTransition.scss +131 -0
- package/src/components/HkListTransition.tsx +29 -0
- package/src/components/HkLocalePickerPopup.tsx +42 -0
- package/src/components/HkLogo.scss +36 -0
- package/src/components/HkLogo.tsx +93 -0
- package/src/components/HkMarkdownRenderer.scss +141 -0
- package/src/components/HkMarkdownRenderer.tsx +123 -0
- package/src/components/HkMediaControlBar.scss +65 -0
- package/src/components/HkMediaControlBar.tsx +115 -0
- package/src/components/HkMediaPlayer.scss +29 -0
- package/src/components/HkMediaPlayer.tsx +210 -0
- package/src/components/HkMediaSlider.scss +54 -0
- package/src/components/HkMediaSlider.tsx +81 -0
- package/src/components/HkMediaVisualizer.scss +7 -0
- package/src/components/HkMediaVisualizer.tsx +119 -0
- package/src/components/HkMinimap.scss +76 -0
- package/src/components/HkMinimap.tsx +255 -0
- package/src/components/HkModal.scss +318 -0
- package/src/components/HkModal.tsx +481 -0
- package/src/components/HkModalBreadcrumb.scss +55 -0
- package/src/components/HkModalBreadcrumb.tsx +113 -0
- package/src/components/HkMorphingTabs.scss +141 -0
- package/src/components/HkMorphingTabs.tsx +113 -0
- package/src/components/HkNavItem.scss +81 -0
- package/src/components/HkNavItem.tsx +157 -0
- package/src/components/HkNumberInput.scss +154 -0
- package/src/components/HkNumberInput.tsx +161 -0
- package/src/components/HkPasswordInput.scss +289 -0
- package/src/components/HkPasswordInput.tsx +533 -0
- package/src/components/HkPhaseTransition.scss +25 -0
- package/src/components/HkPhaseTransition.tsx +40 -0
- package/src/components/HkPopover.scss +82 -0
- package/src/components/HkPopover.tsx +410 -0
- package/src/components/HkPopupSelect.scss +136 -0
- package/src/components/HkPopupSelect.tsx +199 -0
- package/src/components/HkProgressBar.scss +106 -0
- package/src/components/HkProgressBar.tsx +61 -0
- package/src/components/HkProgressDialog.tsx +67 -0
- package/src/components/HkProgressRing.scss +56 -0
- package/src/components/HkProgressRing.tsx +82 -0
- package/src/components/HkRadio.scss +119 -0
- package/src/components/HkRadio.tsx +75 -0
- package/src/components/HkRollingNumber.scss +37 -0
- package/src/components/HkRollingNumber.tsx +97 -0
- package/src/components/HkScrollContainer.scss +101 -0
- package/src/components/HkScrollContainer.tsx +362 -0
- package/src/components/HkSearchInput.scss +132 -0
- package/src/components/HkSearchInput.tsx +117 -0
- package/src/components/HkSelect.scss +143 -0
- package/src/components/HkSelect.tsx +230 -0
- package/src/components/HkSelectionGrid.scss +177 -0
- package/src/components/HkSelectionGrid.tsx +99 -0
- package/src/components/HkSelectionWaterfall.scss +93 -0
- package/src/components/HkSelectionWaterfall.tsx +90 -0
- package/src/components/HkSidebar.scss +81 -0
- package/src/components/HkSidebar.tsx +45 -0
- package/src/components/HkSkeleton.scss +44 -0
- package/src/components/HkSkeleton.tsx +30 -0
- package/src/components/HkSkeletonList.tsx +35 -0
- package/src/components/HkSpinner.scss +67 -0
- package/src/components/HkSpinner.tsx +41 -0
- package/src/components/HkSplash.scss +60 -0
- package/src/components/HkSplash.tsx +40 -0
- package/src/components/HkSwitch.scss +157 -0
- package/src/components/HkSwitch.tsx +72 -0
- package/src/components/HkTable.scss +198 -0
- package/src/components/HkTable.tsx +246 -0
- package/src/components/HkTabs.scss +109 -0
- package/src/components/HkTabs.tsx +111 -0
- package/src/components/HkTag.scss +88 -0
- package/src/components/HkTag.tsx +44 -0
- package/src/components/HkTextarea.scss +72 -0
- package/src/components/HkTextarea.tsx +104 -0
- package/src/components/HkTimeline.scss +139 -0
- package/src/components/HkTimeline.tsx +95 -0
- package/src/components/HkToast.scss +168 -0
- package/src/components/HkToast.tsx +175 -0
- package/src/components/HkTooltip.scss +47 -0
- package/src/components/HkTooltip.tsx +108 -0
- package/src/components/HkTree.scss +188 -0
- package/src/components/HkTree.tsx +217 -0
- package/src/components/HkTrendChart.tsx +180 -0
- package/src/components/HkWindowedItem.scss +17 -0
- package/src/components/HkWindowedItem.tsx +107 -0
- package/src/components/HkZoomToolbar.scss +35 -0
- package/src/components/HkZoomToolbar.tsx +69 -0
- package/src/composables/useBreakpoint.ts +41 -0
- package/src/composables/useClipboard.ts +37 -0
- package/src/composables/useConfirm.ts +35 -0
- package/src/composables/useProgressDialog.ts +52 -0
- package/src/composables/useReportedTransition.ts +69 -0
- package/src/composables/useScrollWindow.ts +21 -0
- package/src/composables/useToast.ts +2 -0
- package/src/demo.scss +24 -0
- package/src/demo.ts +22 -0
- package/src/env.d.ts +6 -0
- package/src/i18n/context.ts +89 -0
- package/src/i18n/locales/ar/components.json +43 -0
- package/src/i18n/locales/ar/components.toml +40 -0
- package/src/i18n/locales/de/components.json +43 -0
- package/src/i18n/locales/de/components.toml +40 -0
- package/src/i18n/locales/en/components.json +43 -0
- package/src/i18n/locales/en/components.toml +40 -0
- package/src/i18n/locales/es/components.json +43 -0
- package/src/i18n/locales/es/components.toml +40 -0
- package/src/i18n/locales/fr/components.json +43 -0
- package/src/i18n/locales/fr/components.toml +40 -0
- package/src/i18n/locales/ja/components.json +43 -0
- package/src/i18n/locales/ja/components.toml +40 -0
- package/src/i18n/locales/ko/components.json +43 -0
- package/src/i18n/locales/ko/components.toml +40 -0
- package/src/i18n/locales/pt/components.json +43 -0
- package/src/i18n/locales/pt/components.toml +40 -0
- package/src/i18n/locales/ru/components.json +43 -0
- package/src/i18n/locales/ru/components.toml +40 -0
- package/src/i18n/locales/zh-Hans/components.json +43 -0
- package/src/i18n/locales/zh-Hans/components.toml +40 -0
- package/src/i18n/locales/zh-Hant/components.json +43 -0
- package/src/i18n/locales/zh-Hant/components.toml +40 -0
- package/src/index.ts +137 -0
- package/src/render.ts +18 -0
- package/src/runtime/animationBus.ts +278 -0
- package/src/runtime/cronBus.ts +25 -0
- package/src/runtime/index.ts +12 -0
- package/src/runtime/useAsyncData.ts +30 -0
- package/src/runtime/useBreakpoint.ts +62 -0
- package/src/runtime/useClipboard.ts +37 -0
- package/src/runtime/useConfirm.ts +35 -0
- package/src/runtime/useOverlay.ts +82 -0
- package/src/runtime/usePopupManager.ts +89 -0
- package/src/runtime/useToast.ts +140 -0
- package/src/styles/index.scss +10 -0
- package/src/theme/index.ts +5 -0
- package/src/theme/presets.ts +307 -0
- package/src/theme/theme.scss +17 -0
- package/src/theme/useBackgroundLuminance.ts +145 -0
- package/src/theme/useSolarTime.ts +101 -0
- package/src/theme/useTheme.ts +141 -0
- package/src/tokens.scss +26 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { reactive } from "vue";
|
|
2
|
+
|
|
3
|
+
import { scheduleCronAfter, type CronHandle } from "./cronBus";
|
|
4
|
+
|
|
5
|
+
export type ToastType = "error" | "success" | "warning" | "info" | "loading";
|
|
6
|
+
|
|
7
|
+
export const TOAST_DURATION: Record<ToastType, number> = {
|
|
8
|
+
error: 30_000,
|
|
9
|
+
warning: 30_000,
|
|
10
|
+
success: 3_000,
|
|
11
|
+
info: 3_000,
|
|
12
|
+
loading: 0,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function isTransient(type: ToastType): boolean {
|
|
16
|
+
return type === "success" || type === "info";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function defaultCopyable(type: ToastType): boolean {
|
|
20
|
+
return type === "error" || type === "warning";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ToastMessage {
|
|
24
|
+
id: number;
|
|
25
|
+
text: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ToastItem {
|
|
29
|
+
id: number;
|
|
30
|
+
type: ToastType;
|
|
31
|
+
messages: ToastMessage[];
|
|
32
|
+
duration?: number;
|
|
33
|
+
copyable?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const state = reactive<{ toasts: ToastItem[] }>({ toasts: [] });
|
|
37
|
+
|
|
38
|
+
let nextSlotId = 0;
|
|
39
|
+
let nextMsgId = 0;
|
|
40
|
+
|
|
41
|
+
const timers = new Map<number, CronHandle>();
|
|
42
|
+
|
|
43
|
+
function clearTimer(slotId: number) {
|
|
44
|
+
const t = timers.get(slotId);
|
|
45
|
+
if (t !== undefined) {
|
|
46
|
+
t.disconnect();
|
|
47
|
+
timers.delete(slotId);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function removeSlot(slotId: number) {
|
|
52
|
+
const idx = state.toasts.findIndex((t) => t.id === slotId);
|
|
53
|
+
if (idx !== -1) state.toasts.splice(idx, 1);
|
|
54
|
+
clearTimer(slotId);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function scheduleAutoDismiss(slot: ToastItem) {
|
|
58
|
+
const duration = slot.duration ?? 0;
|
|
59
|
+
if (duration <= 0) return;
|
|
60
|
+
clearTimer(slot.id);
|
|
61
|
+
timers.set(
|
|
62
|
+
slot.id,
|
|
63
|
+
scheduleCronAfter(() => removeSlot(slot.id), duration),
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function findSlot(type: ToastType): ToastItem | undefined {
|
|
68
|
+
return state.toasts.find((t) => t.type === type);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function push(
|
|
72
|
+
type: ToastType,
|
|
73
|
+
text: string,
|
|
74
|
+
options: { duration?: number; copyable?: boolean } = {},
|
|
75
|
+
): number {
|
|
76
|
+
let slot = findSlot(type);
|
|
77
|
+
if (!slot) {
|
|
78
|
+
slot = {
|
|
79
|
+
id: ++nextSlotId,
|
|
80
|
+
type,
|
|
81
|
+
messages: [],
|
|
82
|
+
duration: options.duration ?? TOAST_DURATION[type],
|
|
83
|
+
copyable: options.copyable ?? defaultCopyable(type),
|
|
84
|
+
};
|
|
85
|
+
state.toasts.push(slot);
|
|
86
|
+
} else if (options.copyable === true && !slot.copyable) {
|
|
87
|
+
slot.copyable = true;
|
|
88
|
+
}
|
|
89
|
+
const msgId = ++nextMsgId;
|
|
90
|
+
slot.messages.push({ id: msgId, text });
|
|
91
|
+
scheduleAutoDismiss(slot);
|
|
92
|
+
return msgId;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function useToast() {
|
|
96
|
+
function show(
|
|
97
|
+
message: string,
|
|
98
|
+
options: Partial<Omit<ToastItem, "id" | "messages">> = {},
|
|
99
|
+
) {
|
|
100
|
+
const type = options.type || "info";
|
|
101
|
+
return push(type, message, {
|
|
102
|
+
duration: options.duration,
|
|
103
|
+
copyable: options.copyable,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function error(message: string, copyable = true) {
|
|
108
|
+
return push("error", message, { copyable });
|
|
109
|
+
}
|
|
110
|
+
function success(message: string) {
|
|
111
|
+
return push("success", message);
|
|
112
|
+
}
|
|
113
|
+
function warning(message: string) {
|
|
114
|
+
return push("warning", message);
|
|
115
|
+
}
|
|
116
|
+
function info(message: string) {
|
|
117
|
+
return push("info", message);
|
|
118
|
+
}
|
|
119
|
+
function loading(message: string, maxDuration = 0) {
|
|
120
|
+
const opts: { duration?: number } = {};
|
|
121
|
+
if (maxDuration > 0) opts.duration = maxDuration;
|
|
122
|
+
return push("loading", message, opts);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function remove(id: number) {
|
|
126
|
+
const slotById = state.toasts.find((t) => t.id === id);
|
|
127
|
+
if (slotById) {
|
|
128
|
+
removeSlot(slotById.id);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
for (const slot of state.toasts) {
|
|
132
|
+
if (slot.messages.some((m) => m.id === id)) {
|
|
133
|
+
removeSlot(slot.id);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { toasts: state.toasts, show, error, success, warning, info, loading, remove };
|
|
140
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Re-export shared hikari theme and component styles.
|
|
2
|
+
// NOTE: The `../../../theme/...` and `../../../components/...` imports below
|
|
3
|
+
// resolve relative to the monorepo root and work locally. When published to
|
|
4
|
+
// npm, consumers who need the full hikari SCSS stack should import this file
|
|
5
|
+
// from a monorepo checkout or bring their own SCSS load-path resolution.
|
|
6
|
+
@use "../../../theme/styles/variables.scss";
|
|
7
|
+
@use "../../../theme/styles/mixins.scss";
|
|
8
|
+
@use "../../../theme/styles/foundation.scss";
|
|
9
|
+
@use "../../../theme/styles/themes.scss";
|
|
10
|
+
@use "../../../components/src/styles/index.scss";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { initTheme, useTheme } from "./useTheme";
|
|
2
|
+
export { themePresets, tokensToCSSVars, getThemeTokens, loadCustomThemes, saveCustomThemes, addCustomTheme, removeCustomTheme } from "./presets";
|
|
3
|
+
export type { ThemeTokenRGB, ThemeSchemeTokens, ThemePreset, CustomThemePreset, ThemeId, ThemeMode } from "./presets";
|
|
4
|
+
export { getTimePeriod, getGeolocation, solarAltitude, DEFAULT_GEO_LOCATION } from "./useSolarTime";
|
|
5
|
+
export type { TimePeriod } from "./useSolarTime";
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
export interface ThemeTokenRGB {
|
|
2
|
+
r: number;
|
|
3
|
+
g: number;
|
|
4
|
+
b: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface ThemeSchemeTokens {
|
|
8
|
+
primary: ThemeTokenRGB;
|
|
9
|
+
secondary: ThemeTokenRGB;
|
|
10
|
+
accent: ThemeTokenRGB;
|
|
11
|
+
text: ThemeTokenRGB;
|
|
12
|
+
muted: ThemeTokenRGB;
|
|
13
|
+
border: ThemeTokenRGB;
|
|
14
|
+
focusedBorder: ThemeTokenRGB;
|
|
15
|
+
background: ThemeTokenRGB;
|
|
16
|
+
surface: ThemeTokenRGB;
|
|
17
|
+
selectedBackground: ThemeTokenRGB;
|
|
18
|
+
selectedText: ThemeTokenRGB;
|
|
19
|
+
statusBarBackground: ThemeTokenRGB;
|
|
20
|
+
success: ThemeTokenRGB;
|
|
21
|
+
error: ThemeTokenRGB;
|
|
22
|
+
warning: ThemeTokenRGB;
|
|
23
|
+
info: ThemeTokenRGB;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ThemePreset {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
dark: ThemeSchemeTokens;
|
|
30
|
+
light: ThemeSchemeTokens;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type ThemeId = string;
|
|
34
|
+
export type ThemeMode = "system" | "light" | "dark";
|
|
35
|
+
|
|
36
|
+
export interface CustomThemePreset {
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
dark: ThemeSchemeTokens;
|
|
40
|
+
light: ThemeSchemeTokens;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const CUSTOM_STORAGE_KEY = "hikari-custom-themes";
|
|
44
|
+
|
|
45
|
+
export function loadCustomThemes(): CustomThemePreset[] {
|
|
46
|
+
try {
|
|
47
|
+
const raw = localStorage.getItem(CUSTOM_STORAGE_KEY);
|
|
48
|
+
return raw ? JSON.parse(raw) : [];
|
|
49
|
+
} catch {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function saveCustomThemes(list: CustomThemePreset[]) {
|
|
55
|
+
localStorage.setItem(CUSTOM_STORAGE_KEY, JSON.stringify(list));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function addCustomTheme(theme: CustomThemePreset) {
|
|
59
|
+
const list = loadCustomThemes();
|
|
60
|
+
const idx = list.findIndex((t) => t.id === theme.id);
|
|
61
|
+
if (idx >= 0) {
|
|
62
|
+
list[idx] = theme;
|
|
63
|
+
} else {
|
|
64
|
+
list.push(theme);
|
|
65
|
+
}
|
|
66
|
+
saveCustomThemes(list);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function removeCustomTheme(id: string) {
|
|
70
|
+
const list = loadCustomThemes().filter((t) => t.id !== id);
|
|
71
|
+
saveCustomThemes(list);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const rgb = (r: number, g: number, b: number): ThemeTokenRGB => ({ r, g, b });
|
|
75
|
+
|
|
76
|
+
export const themePresets: Record<ThemeId, ThemePreset> = {
|
|
77
|
+
synthwave84: {
|
|
78
|
+
id: "synthwave84",
|
|
79
|
+
name: "Synthwave '84",
|
|
80
|
+
dark: {
|
|
81
|
+
primary: rgb(255, 107, 157),
|
|
82
|
+
secondary: rgb(199, 146, 234),
|
|
83
|
+
accent: rgb(253, 235, 139),
|
|
84
|
+
text: rgb(228, 228, 231),
|
|
85
|
+
muted: rgb(180, 180, 180),
|
|
86
|
+
border: rgb(255, 255, 255),
|
|
87
|
+
focusedBorder: rgb(255, 107, 157),
|
|
88
|
+
background: rgb(14, 14, 30),
|
|
89
|
+
surface: rgb(24, 24, 42),
|
|
90
|
+
selectedBackground: rgb(70, 70, 85),
|
|
91
|
+
selectedText: rgb(240, 240, 240),
|
|
92
|
+
statusBarBackground: rgb(24, 24, 42),
|
|
93
|
+
success: rgb(114, 241, 184),
|
|
94
|
+
error: rgb(255, 107, 107),
|
|
95
|
+
warning: rgb(253, 235, 139),
|
|
96
|
+
info: rgb(110, 231, 239),
|
|
97
|
+
},
|
|
98
|
+
light: {
|
|
99
|
+
primary: rgb(214, 51, 132),
|
|
100
|
+
secondary: rgb(156, 106, 222),
|
|
101
|
+
accent: rgb(230, 167, 0),
|
|
102
|
+
text: rgb(30, 30, 30),
|
|
103
|
+
muted: rgb(80, 80, 80),
|
|
104
|
+
border: rgb(0, 0, 0),
|
|
105
|
+
focusedBorder: rgb(214, 51, 132),
|
|
106
|
+
background: rgb(245, 245, 240),
|
|
107
|
+
surface: rgb(255, 255, 255),
|
|
108
|
+
selectedBackground: rgb(200, 200, 205),
|
|
109
|
+
selectedText: rgb(40, 40, 45),
|
|
110
|
+
statusBarBackground: rgb(230, 230, 225),
|
|
111
|
+
success: rgb(16, 185, 129),
|
|
112
|
+
error: rgb(239, 68, 68),
|
|
113
|
+
warning: rgb(245, 158, 11),
|
|
114
|
+
info: rgb(6, 182, 212),
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
nord: {
|
|
118
|
+
id: "nord",
|
|
119
|
+
name: "Nord",
|
|
120
|
+
dark: {
|
|
121
|
+
primary: rgb(136, 192, 208),
|
|
122
|
+
secondary: rgb(143, 188, 187),
|
|
123
|
+
accent: rgb(94, 129, 172),
|
|
124
|
+
text: rgb(236, 239, 244),
|
|
125
|
+
muted: rgb(180, 180, 180),
|
|
126
|
+
border: rgb(255, 255, 255),
|
|
127
|
+
focusedBorder: rgb(136, 192, 208),
|
|
128
|
+
background: rgb(22, 27, 38),
|
|
129
|
+
surface: rgb(34, 40, 54),
|
|
130
|
+
selectedBackground: rgb(70, 70, 85),
|
|
131
|
+
selectedText: rgb(240, 240, 240),
|
|
132
|
+
statusBarBackground: rgb(34, 40, 54),
|
|
133
|
+
success: rgb(163, 190, 140),
|
|
134
|
+
error: rgb(191, 97, 106),
|
|
135
|
+
warning: rgb(208, 135, 112),
|
|
136
|
+
info: rgb(136, 192, 208),
|
|
137
|
+
},
|
|
138
|
+
light: {
|
|
139
|
+
primary: rgb(94, 129, 172),
|
|
140
|
+
secondary: rgb(136, 192, 208),
|
|
141
|
+
accent: rgb(143, 188, 187),
|
|
142
|
+
text: rgb(30, 30, 30),
|
|
143
|
+
muted: rgb(80, 80, 80),
|
|
144
|
+
border: rgb(0, 0, 0),
|
|
145
|
+
focusedBorder: rgb(94, 129, 172),
|
|
146
|
+
background: rgb(236, 239, 244),
|
|
147
|
+
surface: rgb(229, 233, 240),
|
|
148
|
+
selectedBackground: rgb(200, 200, 205),
|
|
149
|
+
selectedText: rgb(40, 40, 45),
|
|
150
|
+
statusBarBackground: rgb(220, 224, 232),
|
|
151
|
+
success: rgb(163, 190, 140),
|
|
152
|
+
error: rgb(191, 97, 106),
|
|
153
|
+
warning: rgb(208, 135, 112),
|
|
154
|
+
info: rgb(136, 192, 208),
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
gruvbox: {
|
|
158
|
+
id: "gruvbox",
|
|
159
|
+
name: "Gruvbox",
|
|
160
|
+
dark: {
|
|
161
|
+
primary: rgb(251, 189, 84),
|
|
162
|
+
secondary: rgb(177, 161, 134),
|
|
163
|
+
accent: rgb(214, 160, 115),
|
|
164
|
+
text: rgb(235, 219, 178),
|
|
165
|
+
muted: rgb(180, 180, 180),
|
|
166
|
+
border: rgb(255, 255, 255),
|
|
167
|
+
focusedBorder: rgb(251, 189, 84),
|
|
168
|
+
background: rgb(20, 20, 20),
|
|
169
|
+
surface: rgb(34, 32, 30),
|
|
170
|
+
selectedBackground: rgb(70, 70, 85),
|
|
171
|
+
selectedText: rgb(240, 240, 240),
|
|
172
|
+
statusBarBackground: rgb(34, 32, 30),
|
|
173
|
+
success: rgb(169, 195, 85),
|
|
174
|
+
error: rgb(251, 118, 118),
|
|
175
|
+
warning: rgb(251, 189, 84),
|
|
176
|
+
info: rgb(131, 191, 152),
|
|
177
|
+
},
|
|
178
|
+
light: {
|
|
179
|
+
primary: rgb(204, 128, 49),
|
|
180
|
+
secondary: rgb(168, 135, 81),
|
|
181
|
+
accent: rgb(186, 128, 82),
|
|
182
|
+
text: rgb(30, 30, 30),
|
|
183
|
+
muted: rgb(80, 80, 80),
|
|
184
|
+
border: rgb(0, 0, 0),
|
|
185
|
+
focusedBorder: rgb(204, 128, 49),
|
|
186
|
+
background: rgb(251, 241, 199),
|
|
187
|
+
surface: rgb(242, 229, 188),
|
|
188
|
+
selectedBackground: rgb(200, 200, 205),
|
|
189
|
+
selectedText: rgb(40, 40, 45),
|
|
190
|
+
statusBarBackground: rgb(235, 225, 185),
|
|
191
|
+
success: rgb(121, 153, 50),
|
|
192
|
+
error: rgb(204, 69, 57),
|
|
193
|
+
warning: rgb(204, 128, 49),
|
|
194
|
+
info: rgb(70, 120, 104),
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
tokyonight: {
|
|
198
|
+
id: "tokyonight",
|
|
199
|
+
name: "Tokyo Night",
|
|
200
|
+
dark: {
|
|
201
|
+
primary: rgb(122, 162, 247),
|
|
202
|
+
secondary: rgb(125, 207, 255),
|
|
203
|
+
accent: rgb(247, 118, 142),
|
|
204
|
+
text: rgb(192, 202, 245),
|
|
205
|
+
muted: rgb(180, 180, 180),
|
|
206
|
+
border: rgb(255, 255, 255),
|
|
207
|
+
focusedBorder: rgb(122, 162, 247),
|
|
208
|
+
background: rgb(14, 15, 24),
|
|
209
|
+
surface: rgb(22, 24, 36),
|
|
210
|
+
selectedBackground: rgb(70, 70, 85),
|
|
211
|
+
selectedText: rgb(240, 240, 240),
|
|
212
|
+
statusBarBackground: rgb(22, 24, 36),
|
|
213
|
+
success: rgb(158, 206, 106),
|
|
214
|
+
error: rgb(247, 118, 142),
|
|
215
|
+
warning: rgb(255, 183, 87),
|
|
216
|
+
info: rgb(125, 207, 255),
|
|
217
|
+
},
|
|
218
|
+
light: {
|
|
219
|
+
primary: rgb(52, 96, 189),
|
|
220
|
+
secondary: rgb(56, 157, 192),
|
|
221
|
+
accent: rgb(225, 72, 96),
|
|
222
|
+
text: rgb(30, 30, 30),
|
|
223
|
+
muted: rgb(80, 80, 80),
|
|
224
|
+
border: rgb(0, 0, 0),
|
|
225
|
+
focusedBorder: rgb(52, 96, 189),
|
|
226
|
+
background: rgb(231, 233, 241),
|
|
227
|
+
surface: rgb(221, 223, 231),
|
|
228
|
+
selectedBackground: rgb(200, 200, 205),
|
|
229
|
+
selectedText: rgb(40, 40, 45),
|
|
230
|
+
statusBarBackground: rgb(215, 217, 227),
|
|
231
|
+
success: rgb(86, 159, 86),
|
|
232
|
+
error: rgb(225, 72, 96),
|
|
233
|
+
warning: rgb(206, 145, 60),
|
|
234
|
+
info: rgb(56, 157, 192),
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export type ThemeTokens = ThemeSchemeTokens;
|
|
240
|
+
|
|
241
|
+
export function getThemeTokens(
|
|
242
|
+
name: string,
|
|
243
|
+
mode: string,
|
|
244
|
+
): ThemeSchemeTokens | null {
|
|
245
|
+
const preset = themePresets[name as keyof typeof themePresets];
|
|
246
|
+
if (preset) return mode === "dark" ? preset.dark : preset.light;
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function contrastColor({ r, g, b }: ThemeTokenRGB): ThemeTokenRGB {
|
|
251
|
+
const luminance = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;
|
|
252
|
+
return luminance > 0.55 ? { r: 0, g: 0, b: 0 } : { r: 255, g: 255, b: 255 };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function mixToken(a: ThemeTokenRGB, b: ThemeTokenRGB, t: number): ThemeTokenRGB {
|
|
256
|
+
return {
|
|
257
|
+
r: Math.round(a.r + (b.r - a.r) * t),
|
|
258
|
+
g: Math.round(a.g + (b.g - a.g) * t),
|
|
259
|
+
b: Math.round(a.b + (b.b - a.b) * t),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function tokensToCSSVars(
|
|
264
|
+
tokens: ThemeSchemeTokens,
|
|
265
|
+
): Record<string, string> {
|
|
266
|
+
const onPrimary = contrastColor(tokens.primary);
|
|
267
|
+
const textSecondary = mixToken(tokens.text, tokens.muted, 0.25);
|
|
268
|
+
return {
|
|
269
|
+
"--color-primary": `${tokens.primary.r} ${tokens.primary.g} ${tokens.primary.b}`,
|
|
270
|
+
"--color-on-primary": `${onPrimary.r} ${onPrimary.g} ${onPrimary.b}`,
|
|
271
|
+
"--color-on-solid": "255 255 255",
|
|
272
|
+
"--color-secondary": `${tokens.secondary.r} ${tokens.secondary.g} ${tokens.secondary.b}`,
|
|
273
|
+
"--color-accent": `${tokens.accent.r} ${tokens.accent.g} ${tokens.accent.b}`,
|
|
274
|
+
"--color-text": `${tokens.text.r} ${tokens.text.g} ${tokens.text.b}`,
|
|
275
|
+
"--color-text-secondary": `${textSecondary.r} ${textSecondary.g} ${textSecondary.b}`,
|
|
276
|
+
"--color-muted": `${tokens.muted.r} ${tokens.muted.g} ${tokens.muted.b}`,
|
|
277
|
+
"--color-border": `${tokens.border.r} ${tokens.border.g} ${tokens.border.b}`,
|
|
278
|
+
"--color-focused-border": `${tokens.focusedBorder.r} ${tokens.focusedBorder.g} ${tokens.focusedBorder.b}`,
|
|
279
|
+
"--color-background": `${tokens.background.r} ${tokens.background.g} ${tokens.background.b}`,
|
|
280
|
+
"--color-surface": `${tokens.surface.r} ${tokens.surface.g} ${tokens.surface.b}`,
|
|
281
|
+
"--color-selected-bg": `${tokens.selectedBackground.r} ${tokens.selectedBackground.g} ${tokens.selectedBackground.b}`,
|
|
282
|
+
"--color-selected-text": `${tokens.selectedText.r} ${tokens.selectedText.g} ${tokens.selectedText.b}`,
|
|
283
|
+
"--color-status-bar-bg": `${tokens.statusBarBackground.r} ${tokens.statusBarBackground.g} ${tokens.statusBarBackground.b}`,
|
|
284
|
+
"--color-success": `${tokens.success.r} ${tokens.success.g} ${tokens.success.b}`,
|
|
285
|
+
"--color-error": `${tokens.error.r} ${tokens.error.g} ${tokens.error.b}`,
|
|
286
|
+
"--color-warning": `${tokens.warning.r} ${tokens.warning.g} ${tokens.warning.b}`,
|
|
287
|
+
"--color-info": `${tokens.info.r} ${tokens.info.g} ${tokens.info.b}`,
|
|
288
|
+
"--hi-color-primary": "rgb(var(--color-primary))",
|
|
289
|
+
"--hi-color-secondary": "rgb(var(--color-secondary))",
|
|
290
|
+
"--hi-color-surface": "rgb(var(--color-surface))",
|
|
291
|
+
"--hi-color-background": "rgb(var(--color-background))",
|
|
292
|
+
"--hi-color-border": "rgb(var(--color-border))",
|
|
293
|
+
"--hi-color-text-primary": "rgb(var(--color-text))",
|
|
294
|
+
"--hi-color-text-secondary": "rgb(var(--color-text-secondary))",
|
|
295
|
+
"--hi-color-muted": "rgb(var(--color-muted))",
|
|
296
|
+
"--hi-color-on-primary": "rgb(var(--color-on-primary))",
|
|
297
|
+
"--hi-color-text-on-primary": "rgb(var(--color-on-primary))",
|
|
298
|
+
"--hi-color-success": "rgb(var(--color-success))",
|
|
299
|
+
"--hi-color-error": "rgb(var(--color-error))",
|
|
300
|
+
"--hi-color-warning": "rgb(var(--color-warning))",
|
|
301
|
+
"--hi-color-info": "rgb(var(--color-info))",
|
|
302
|
+
"--hi-color-bg-subtle": "rgb(var(--color-surface) / 0.5)",
|
|
303
|
+
"--hi-color-bg-elevated": "rgb(var(--color-surface))",
|
|
304
|
+
"--hi-color-bg-canvas": "rgb(var(--color-background))",
|
|
305
|
+
"--hi-secondary-bg": "rgb(var(--color-surface) / 0.5)",
|
|
306
|
+
};
|
|
307
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Essential theme CSS variables for hikari-vue components
|
|
2
|
+
// Color/radius/space/text tokens are NOT defined here —
|
|
3
|
+
// they come from the consumer's theme bridge.
|
|
4
|
+
// Component SCSS has fallback values in every var() call.
|
|
5
|
+
|
|
6
|
+
:root {
|
|
7
|
+
--hi-z-above: 1;
|
|
8
|
+
--hi-z-tooltip: 10000;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
[data-mode="dark"] body {
|
|
12
|
+
color-scheme: dark;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
[data-mode="light"] body {
|
|
16
|
+
color-scheme: light;
|
|
17
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { onceFrame, onFrame, type AnimationHandle } from "../runtime/animationBus";
|
|
2
|
+
|
|
3
|
+
const CVAR = "--float-text-color";
|
|
4
|
+
const CVAR_BADGE = "--float-badge-color";
|
|
5
|
+
|
|
6
|
+
let handle: AnimationHandle | null = null;
|
|
7
|
+
let cachedText: string | null = null;
|
|
8
|
+
let cachedPrimary: string | null = null;
|
|
9
|
+
let pendingPBO: { buffer: WebGLBuffer; gl: WebGL2RenderingContext } | null = null;
|
|
10
|
+
|
|
11
|
+
function luminance(r: number, g: number, b: number): number {
|
|
12
|
+
return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function applyLuminance(lum: number) {
|
|
16
|
+
const el = document.documentElement;
|
|
17
|
+
if (lum < 0.45) {
|
|
18
|
+
el.style.setProperty(CVAR, "255, 255, 255");
|
|
19
|
+
el.style.setProperty(CVAR_BADGE, "255, 255, 255");
|
|
20
|
+
} else {
|
|
21
|
+
if (cachedText === null || cachedPrimary === null) {
|
|
22
|
+
const cs = getComputedStyle(el);
|
|
23
|
+
cachedText = cs.getPropertyValue("--color-text").trim();
|
|
24
|
+
cachedPrimary = cs.getPropertyValue("--color-primary").trim();
|
|
25
|
+
}
|
|
26
|
+
el.style.setProperty(CVAR, cachedText);
|
|
27
|
+
el.style.setProperty(CVAR_BADGE, cachedPrimary);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function sampleShader(): boolean {
|
|
32
|
+
const canvas = document.getElementById("s-wallpaper-canvas") as HTMLCanvasElement | null;
|
|
33
|
+
if (!canvas) return false;
|
|
34
|
+
const gl = canvas.getContext("webgl2");
|
|
35
|
+
if (!gl) return false;
|
|
36
|
+
|
|
37
|
+
let applied = false;
|
|
38
|
+
|
|
39
|
+
if (pendingPBO && pendingPBO.gl === gl) {
|
|
40
|
+
try {
|
|
41
|
+
const data = new Uint8Array(4);
|
|
42
|
+
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pendingPBO.buffer);
|
|
43
|
+
gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, data, 0, 4);
|
|
44
|
+
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
|
|
45
|
+
gl.deleteBuffer(pendingPBO.buffer);
|
|
46
|
+
applyLuminance(luminance(data[0], data[1], data[2]));
|
|
47
|
+
applied = true;
|
|
48
|
+
} catch {
|
|
49
|
+
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
|
|
50
|
+
gl.deleteBuffer(pendingPBO.buffer);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
pendingPBO = null;
|
|
54
|
+
|
|
55
|
+
const pbo = gl.createBuffer();
|
|
56
|
+
if (pbo) {
|
|
57
|
+
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo);
|
|
58
|
+
gl.bufferData(gl.PIXEL_PACK_BUFFER, 4, gl.STREAM_READ);
|
|
59
|
+
const cx = Math.floor(canvas.width / 2);
|
|
60
|
+
const cy = Math.floor(canvas.height / 2);
|
|
61
|
+
gl.readPixels(cx, cy, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, 0);
|
|
62
|
+
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
|
|
63
|
+
pendingPBO = { buffer: pbo, gl };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return applied;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function sampleMedia(el: HTMLImageElement | HTMLVideoElement): boolean {
|
|
70
|
+
try {
|
|
71
|
+
let w: number, h: number;
|
|
72
|
+
if (el instanceof HTMLImageElement) {
|
|
73
|
+
w = el.naturalWidth || 1;
|
|
74
|
+
h = el.naturalHeight || 1;
|
|
75
|
+
} else {
|
|
76
|
+
w = el.videoWidth || 1;
|
|
77
|
+
h = el.videoHeight || 1;
|
|
78
|
+
}
|
|
79
|
+
w = Math.min(w, 64);
|
|
80
|
+
h = Math.min(h, 64);
|
|
81
|
+
const c = document.createElement("canvas");
|
|
82
|
+
c.width = 1;
|
|
83
|
+
c.height = 1;
|
|
84
|
+
const ctx = c.getContext("2d");
|
|
85
|
+
if (!ctx) return false;
|
|
86
|
+
ctx.drawImage(el, w / 2, h / 2, 1, 1, 0, 0, 1, 1);
|
|
87
|
+
const [r, g, b] = ctx.getImageData(0, 0, 1, 1).data;
|
|
88
|
+
applyLuminance(luminance(r, g, b));
|
|
89
|
+
return true;
|
|
90
|
+
} catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function sampleFromBodyBg(): boolean {
|
|
96
|
+
const raw = getComputedStyle(document.body).backgroundColor;
|
|
97
|
+
const m = raw.match(/\d+/g);
|
|
98
|
+
if (!m || m.length < 3) return false;
|
|
99
|
+
applyLuminance(luminance(+m[0], +m[1], +m[2]));
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function sampleFrame() {
|
|
104
|
+
if (sampleShader()) return;
|
|
105
|
+
const video = document.getElementById("s-wallpaper-video") as HTMLVideoElement | null;
|
|
106
|
+
if (video && video.readyState >= 2 && sampleMedia(video)) return;
|
|
107
|
+
const bgImg = getComputedStyle(document.body).backgroundImage;
|
|
108
|
+
if (bgImg && bgImg !== "none") {
|
|
109
|
+
const urlMatch = bgImg.match(/url\(["']?(.*?)["']?\)/);
|
|
110
|
+
if (urlMatch) {
|
|
111
|
+
const img = document.createElement("img");
|
|
112
|
+
img.crossOrigin = "anonymous";
|
|
113
|
+
img.src = urlMatch[1];
|
|
114
|
+
img.onload = () => { sampleMedia(img); };
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
sampleFromBodyBg();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function startLuminanceSampler() {
|
|
122
|
+
stopLuminanceSampler();
|
|
123
|
+
sampleFrame();
|
|
124
|
+
handle = onFrame(() => sampleFrame(), "idle");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function stopLuminanceSampler() {
|
|
128
|
+
if (handle) {
|
|
129
|
+
handle.disconnect();
|
|
130
|
+
handle = null;
|
|
131
|
+
}
|
|
132
|
+
if (pendingPBO) {
|
|
133
|
+
try { pendingPBO.gl.deleteBuffer(pendingPBO.buffer); } catch {}
|
|
134
|
+
pendingPBO = null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function sampleLuminanceNow() {
|
|
139
|
+
onceFrame(() => sampleFrame());
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function invalidateLuminanceCache() {
|
|
143
|
+
cachedText = null;
|
|
144
|
+
cachedPrimary = null;
|
|
145
|
+
}
|