@astrake/lumora-ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/package.json +54 -0
  2. package/src/components/LuAvatar.vue +22 -0
  3. package/src/components/LuBadge.vue +15 -0
  4. package/src/components/LuButton.vue +58 -0
  5. package/src/components/LuCard.vue +20 -0
  6. package/src/components/LuCollapsible.vue +34 -0
  7. package/src/components/LuDivider.vue +18 -0
  8. package/src/components/LuIcon.vue +39 -0
  9. package/src/components/LuInput.vue +30 -0
  10. package/src/components/LuLink.vue +47 -0
  11. package/src/components/LuPageHeader.vue +24 -0
  12. package/src/components/LuProgressBar.vue +21 -0
  13. package/src/components/LuSelect.vue +35 -0
  14. package/src/components/LuSwitch.vue +40 -0
  15. package/src/components/LuTab.vue +26 -0
  16. package/src/components/LuTabList.vue +15 -0
  17. package/src/components/LuTabPanel.vue +19 -0
  18. package/src/components/LuTable.vue +15 -0
  19. package/src/components/LuTableBody.vue +15 -0
  20. package/src/components/LuTableCell.vue +15 -0
  21. package/src/components/LuTableHead.vue +15 -0
  22. package/src/components/LuTableHeadCell.vue +15 -0
  23. package/src/components/LuTableRow.vue +15 -0
  24. package/src/components/LuTabs.vue +30 -0
  25. package/src/components/LuText.vue +18 -0
  26. package/src/components/LuThemeSelect.vue +26 -0
  27. package/src/components/LuThemeSwitch.vue +22 -0
  28. package/src/components/LuTooltip.vue +36 -0
  29. package/src/components/index.ts +27 -0
  30. package/src/composables/index.ts +4 -0
  31. package/src/composables/useRail.ts +24 -0
  32. package/src/composables/useSplit.ts +17 -0
  33. package/src/composables/useTheme.ts +36 -0
  34. package/src/context.ts +36 -0
  35. package/src/index.ts +8 -0
  36. package/src/layout/LuDock.vue +23 -0
  37. package/src/layout/LuDockItem.vue +18 -0
  38. package/src/layout/LuFill.vue +17 -0
  39. package/src/layout/LuFixed.vue +17 -0
  40. package/src/layout/LuGrid.vue +22 -0
  41. package/src/layout/LuOverlay.vue +17 -0
  42. package/src/layout/LuScroll.vue +17 -0
  43. package/src/layout/LuSplit.vue +23 -0
  44. package/src/layout/LuSplitPane.vue +32 -0
  45. package/src/layout/LuSplitResizer.vue +17 -0
  46. package/src/layout/LuStack.vue +24 -0
  47. package/src/layout/index.ts +25 -0
  48. package/src/plugin.ts +27 -0
  49. package/src/shell/desktop/LuDesktopRailBar.vue +23 -0
  50. package/src/shell/desktop/LuDesktopRailItem.vue +23 -0
  51. package/src/shell/desktop/LuDesktopShell.vue +25 -0
  52. package/src/shell/desktop/LuDesktopSidebar.vue +36 -0
  53. package/src/shell/desktop/LuDesktopStatusBar.vue +15 -0
  54. package/src/shell/desktop/LuDesktopTopBar.vue +15 -0
  55. package/src/shell/embedded/LuEmbeddedShell.vue +20 -0
  56. package/src/shell/index.ts +10 -0
  57. package/src/shell/mobile/LuMobileShell.vue +21 -0
  58. package/src/skins/default.ts +94 -0
  59. package/src/skins/index.ts +1 -0
  60. package/src/types.ts +18 -0
  61. package/tsconfig.json +10 -0
@@ -0,0 +1,18 @@
1
+ <template>
2
+ <component :is="as ?? 'span'" v-bind="$attrs" :class="resolvedSkin">
3
+ <slot />
4
+ </component>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed } from "vue";
9
+ import { useLumoraConfig } from "../context";
10
+
11
+ const props = defineProps<{
12
+ variant?: string;
13
+ as?: string;
14
+ }>();
15
+
16
+ const { resolveSkin } = useLumoraConfig();
17
+ const resolvedSkin = computed(() => resolveSkin("LuText", props.variant));
18
+ </script>
@@ -0,0 +1,26 @@
1
+ <template>
2
+ <LuSelect
3
+ :model-value="mode"
4
+ variant="theme"
5
+ :options="options"
6
+ v-bind="$attrs"
7
+ @update:model-value="onChange"
8
+ />
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ import LuSelect from "./LuSelect.vue";
13
+ import { useTheme, type ThemeMode } from "../composables/useTheme";
14
+
15
+ const { mode, setMode } = useTheme();
16
+
17
+ const options = [
18
+ { value: "system", label: "System" },
19
+ { value: "light", label: "Light" },
20
+ { value: "dark", label: "Dark" },
21
+ ];
22
+
23
+ const onChange = (val: string) => {
24
+ setMode(val as ThemeMode);
25
+ };
26
+ </script>
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <LuSwitch
3
+ :model-value="isDark"
4
+ variant="theme"
5
+ v-bind="$attrs"
6
+ @update:model-value="onToggle"
7
+ />
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ import { computed } from "vue";
12
+ import LuSwitch from "./LuSwitch.vue";
13
+ import { useTheme } from "../composables/useTheme";
14
+
15
+ const { resolved, setMode } = useTheme();
16
+
17
+ const isDark = computed(() => resolved.value === "dark");
18
+
19
+ const onToggle = (val: boolean) => {
20
+ setMode(val ? "dark" : "light");
21
+ };
22
+ </script>
@@ -0,0 +1,36 @@
1
+ <template>
2
+ <div v-bind="$attrs" :class="containerSkin" @mouseenter="onEnter" @mouseleave="onLeave">
3
+ <slot name="trigger" />
4
+ <div v-if="isVisible" :class="contentSkin">
5
+ <slot name="content" />
6
+ </div>
7
+ </div>
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ import { computed, ref } from "vue";
12
+ import { useLumoraConfig } from "../context";
13
+
14
+ const props = defineProps<{ variant?: string; position?: "top" | "bottom" | "left" | "right"; delay?: number }>();
15
+
16
+ const isVisible = ref(false);
17
+ let timeout: ReturnType<typeof setTimeout> | null = null;
18
+
19
+ const onEnter = () => {
20
+ if (timeout) clearTimeout(timeout);
21
+ if (props.delay) {
22
+ timeout = setTimeout(() => isVisible.value = true, props.delay);
23
+ } else {
24
+ isVisible.value = true;
25
+ }
26
+ };
27
+
28
+ const onLeave = () => {
29
+ if (timeout) clearTimeout(timeout);
30
+ isVisible.value = false;
31
+ };
32
+
33
+ const { resolveSkin } = useLumoraConfig();
34
+ const containerSkin = computed(() => resolveSkin("LuTooltip", props.variant));
35
+ const contentSkin = computed(() => resolveSkin("LuTooltipContent", props.position ?? props.variant));
36
+ </script>
@@ -0,0 +1,27 @@
1
+ export { default as LuButton } from "./LuButton.vue";
2
+ export { default as LuInput } from "./LuInput.vue";
3
+ export { default as LuIcon } from "./LuIcon.vue";
4
+ export { default as LuText } from "./LuText.vue";
5
+ export { default as LuSwitch } from "./LuSwitch.vue";
6
+ export { default as LuSelect } from "./LuSelect.vue";
7
+ export { default as LuThemeSwitch } from "./LuThemeSwitch.vue";
8
+ export { default as LuThemeSelect } from "./LuThemeSelect.vue";
9
+ export { default as LuTabs } from "./LuTabs.vue";
10
+ export { default as LuTabList } from "./LuTabList.vue";
11
+ export { default as LuTab } from "./LuTab.vue";
12
+ export { default as LuTabPanel } from "./LuTabPanel.vue";
13
+ export { default as LuBadge } from "./LuBadge.vue";
14
+ export { default as LuProgressBar } from "./LuProgressBar.vue";
15
+ export { default as LuTooltip } from "./LuTooltip.vue";
16
+ export { default as LuCollapsible } from "./LuCollapsible.vue";
17
+ export { default as LuAvatar } from "./LuAvatar.vue";
18
+ export { default as LuLink } from "./LuLink.vue";
19
+ export { default as LuDivider } from "./LuDivider.vue";
20
+ export { default as LuPageHeader } from "./LuPageHeader.vue";
21
+ export { default as LuCard } from "./LuCard.vue";
22
+ export { default as LuTable } from "./LuTable.vue";
23
+ export { default as LuTableHead } from "./LuTableHead.vue";
24
+ export { default as LuTableBody } from "./LuTableBody.vue";
25
+ export { default as LuTableRow } from "./LuTableRow.vue";
26
+ export { default as LuTableHeadCell } from "./LuTableHeadCell.vue";
27
+ export { default as LuTableCell } from "./LuTableCell.vue";
@@ -0,0 +1,4 @@
1
+ export { useRail } from "./useRail";
2
+ export { useSplit } from "./useSplit";
3
+ export { useTheme, type ThemeMode, type ResolvedTheme } from "./useTheme";
4
+ // useLumoraConfig is exported from context.ts
@@ -0,0 +1,24 @@
1
+ import { ref, readonly } from "vue";
2
+
3
+ export function useRail(defaultExpanded = false) {
4
+ const isExpanded = ref(defaultExpanded);
5
+
6
+ function toggle() {
7
+ isExpanded.value = !isExpanded.value;
8
+ }
9
+
10
+ function expand() {
11
+ isExpanded.value = true;
12
+ }
13
+
14
+ function collapse() {
15
+ isExpanded.value = false;
16
+ }
17
+
18
+ return {
19
+ isExpanded: readonly(isExpanded),
20
+ toggle,
21
+ expand,
22
+ collapse,
23
+ };
24
+ }
@@ -0,0 +1,17 @@
1
+ import { ref, type Ref } from "vue";
2
+
3
+ export function useSplit(
4
+ containerRef: Ref<HTMLElement | null>,
5
+ direction: "horizontal" | "vertical" = "horizontal",
6
+ onResize?: (sizes: number[]) => void
7
+ ) {
8
+ const isDragging = ref(false);
9
+
10
+ // Simplified split drag logic hook.
11
+ // In a real implementation, this would attach mousedown to resizers,
12
+ // and mousemove/mouseup to the window, updating the size of adjacent panes.
13
+
14
+ return {
15
+ isDragging,
16
+ };
17
+ }
@@ -0,0 +1,36 @@
1
+ import { ref, computed, watchEffect } from "vue";
2
+
3
+ export type ThemeMode = "system" | "light" | "dark";
4
+ export type ResolvedTheme = "light" | "dark";
5
+
6
+ // Global state for the theme
7
+ const mode = ref<ThemeMode>("system");
8
+ const systemPrefersDark = ref(false);
9
+
10
+ // Update system preference when window matches change
11
+ if (typeof window !== "undefined") {
12
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
13
+ systemPrefersDark.value = mediaQuery.matches;
14
+ mediaQuery.addEventListener("change", (e) => {
15
+ systemPrefersDark.value = e.matches;
16
+ });
17
+ }
18
+
19
+ const resolved = computed<ResolvedTheme>(() => {
20
+ if (mode.value === "system") {
21
+ return systemPrefersDark.value ? "dark" : "light";
22
+ }
23
+ return mode.value;
24
+ });
25
+
26
+ export function useTheme() {
27
+ const setMode = (newMode: ThemeMode) => {
28
+ mode.value = newMode;
29
+ };
30
+
31
+ return {
32
+ mode,
33
+ resolved,
34
+ setMode,
35
+ };
36
+ }
package/src/context.ts ADDED
@@ -0,0 +1,36 @@
1
+ import { inject, isRef, type InjectionKey, type Component } from "vue";
2
+ import type { LumoraUIConfig, SkinMap } from "./types";
3
+
4
+ export const LumoraUIConfigKey: InjectionKey<LumoraUIConfig> = Symbol("LumoraUIConfig");
5
+
6
+ export function useLumoraConfig() {
7
+ const config = inject(LumoraUIConfigKey, {});
8
+
9
+ const resolveSkin = (componentName: string, variant?: string): string => {
10
+ if (!config.skin) return "";
11
+
12
+ // Unwrap the skin map if it's a ref
13
+ const skinMap = isRef(config.skin) ? config.skin.value : config.skin;
14
+ if (!skinMap) return "";
15
+
16
+ const componentSkin = skinMap[componentName];
17
+ if (!componentSkin) return "";
18
+
19
+ const classes: string[] = [];
20
+ if (componentSkin.default) {
21
+ classes.push(componentSkin.default);
22
+ }
23
+ if (variant && componentSkin[variant]) {
24
+ classes.push(componentSkin[variant] as string);
25
+ }
26
+
27
+ return classes.join(" ");
28
+ };
29
+
30
+ const resolveIcon = (name: string, size?: number): Component | null => {
31
+ if (!config.icons) return null;
32
+ return config.icons(name, size);
33
+ };
34
+
35
+ return { config, resolveSkin, resolveIcon };
36
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export * from "./types";
2
+ export * from "./context";
3
+ export * from "./plugin";
4
+
5
+ export * from "./layout/index";
6
+ export * from "./shell/index";
7
+ export * from "./components/index";
8
+ export * from "./skins/index";
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <div v-bind="$attrs" :class="resolvedSkin">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed } from "vue";
9
+ import { useLumoraConfig } from "../context";
10
+
11
+ const props = defineProps<{
12
+ direction?: "vertical" | "horizontal";
13
+ variant?: string;
14
+ }>();
15
+
16
+ const { resolveSkin } = useLumoraConfig();
17
+ const resolvedSkin = computed(() =>
18
+ resolveSkin("LuDock", props.direction ?? props.variant) ||
19
+ (props.direction === "horizontal"
20
+ ? "flex flex-row h-full w-full overflow-hidden"
21
+ : "flex flex-col h-full w-full overflow-hidden")
22
+ );
23
+ </script>
@@ -0,0 +1,18 @@
1
+ <template>
2
+ <div v-bind="$attrs" :class="resolvedSkin">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed } from "vue";
9
+ import { useLumoraConfig } from "../context";
10
+
11
+ const props = defineProps<{
12
+ dock?: "top" | "bottom" | "left" | "right" | "fill";
13
+ variant?: string;
14
+ }>();
15
+
16
+ const { resolveSkin } = useLumoraConfig();
17
+ const resolvedSkin = computed(() => resolveSkin("LuDockItem", props.dock ?? props.variant));
18
+ </script>
@@ -0,0 +1,17 @@
1
+ <template>
2
+ <div v-bind="$attrs" :class="resolvedSkin">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed } from "vue";
9
+ import { useLumoraConfig } from "../context";
10
+
11
+ const props = defineProps<{
12
+ variant?: string;
13
+ }>();
14
+
15
+ const { resolveSkin } = useLumoraConfig();
16
+ const resolvedSkin = computed(() => resolveSkin("LuFill", props.variant));
17
+ </script>
@@ -0,0 +1,17 @@
1
+ <template>
2
+ <div v-bind="$attrs" :class="resolvedSkin">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed } from "vue";
9
+ import { useLumoraConfig } from "../context";
10
+
11
+ const props = defineProps<{
12
+ variant?: string;
13
+ }>();
14
+
15
+ const { resolveSkin } = useLumoraConfig();
16
+ const resolvedSkin = computed(() => resolveSkin("LuFixed", props.variant));
17
+ </script>
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <div v-bind="$attrs" :class="resolvedSkin" :style="gridStyle">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed } from "vue";
9
+ import { useLumoraConfig } from "../context";
10
+
11
+ const props = defineProps<{
12
+ cols?: number;
13
+ variant?: string;
14
+ }>();
15
+
16
+ const { resolveSkin } = useLumoraConfig();
17
+ const resolvedSkin = computed(() => resolveSkin("LuGrid", props.variant) || "grid");
18
+
19
+ const gridStyle = computed(() =>
20
+ props.cols ? { gridTemplateColumns: `repeat(${props.cols}, minmax(0, 1fr))` } : {}
21
+ );
22
+ </script>
@@ -0,0 +1,17 @@
1
+ <template>
2
+ <div v-bind="$attrs" :class="resolvedSkin">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed } from "vue";
9
+ import { useLumoraConfig } from "../context";
10
+
11
+ const props = defineProps<{
12
+ variant?: string;
13
+ }>();
14
+
15
+ const { resolveSkin } = useLumoraConfig();
16
+ const resolvedSkin = computed(() => resolveSkin("LuOverlay", props.variant));
17
+ </script>
@@ -0,0 +1,17 @@
1
+ <template>
2
+ <div v-bind="$attrs" :class="resolvedSkin">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed } from "vue";
9
+ import { useLumoraConfig } from "../context";
10
+
11
+ const props = defineProps<{
12
+ variant?: string;
13
+ }>();
14
+
15
+ const { resolveSkin } = useLumoraConfig();
16
+ const resolvedSkin = computed(() => resolveSkin("LuScroll", props.variant));
17
+ </script>
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <div v-bind="$attrs" :class="resolvedSkin" ref="splitRef">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed, provide, ref, readonly } from "vue";
9
+ import { useLumoraConfig } from "../context";
10
+
11
+ const props = defineProps<{
12
+ direction?: "horizontal" | "vertical";
13
+ variant?: string;
14
+ }>();
15
+
16
+ const direction = computed(() => props.direction ?? "horizontal");
17
+ provide("lu-split-direction", readonly(direction));
18
+
19
+ const splitRef = ref<HTMLElement | null>(null);
20
+
21
+ const { resolveSkin } = useLumoraConfig();
22
+ const resolvedSkin = computed(() => resolveSkin("LuSplit", props.direction ?? props.variant));
23
+ </script>
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <div v-bind="$attrs" :class="resolvedSkin" :style="style">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed, inject, ref } from "vue";
9
+ import { useLumoraConfig } from "../context";
10
+
11
+ const props = defineProps<{
12
+ minSize?: number;
13
+ maxSize?: number;
14
+ defaultSize?: number;
15
+ variant?: string;
16
+ }>();
17
+
18
+ const direction = inject<"horizontal" | "vertical">("lu-split-direction", "horizontal");
19
+ const size = ref(props.defaultSize);
20
+
21
+ const style = computed(() => {
22
+ if (size.value === undefined) return { flex: "1 1 0%" };
23
+ const dim = `${size.value}px`;
24
+ return direction === "horizontal"
25
+ ? { width: dim, flex: `0 0 ${dim}` }
26
+ : { height: dim, flex: `0 0 ${dim}` };
27
+ });
28
+
29
+
30
+ const { resolveSkin } = useLumoraConfig();
31
+ const resolvedSkin = computed(() => resolveSkin("LuSplitPane", props.variant));
32
+ </script>
@@ -0,0 +1,17 @@
1
+ <template>
2
+ <div v-bind="$attrs" :class="resolvedSkin"></div>
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ import { computed, inject } from "vue";
7
+ import { useLumoraConfig } from "../context";
8
+
9
+ const props = defineProps<{
10
+ variant?: string;
11
+ }>();
12
+
13
+ const direction = inject<"horizontal" | "vertical">("lu-split-direction", "horizontal");
14
+
15
+ const { resolveSkin } = useLumoraConfig();
16
+ const resolvedSkin = computed(() => resolveSkin("LuSplitResizer", direction === "horizontal" ? "horizontal" : "vertical"));
17
+ </script>
@@ -0,0 +1,24 @@
1
+ <template>
2
+ <div v-bind="$attrs" :class="resolvedSkin">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed } from "vue";
9
+ import { useLumoraConfig } from "../context";
10
+
11
+ const props = defineProps<{
12
+ direction?: "vertical" | "horizontal";
13
+ variant?: string;
14
+ }>();
15
+
16
+ const { resolveSkin } = useLumoraConfig();
17
+ const resolvedSkin = computed(() => {
18
+ const base = resolveSkin("LuStack", props.variant);
19
+ let extra = "";
20
+ if (props.direction === "vertical") extra = " flex-col";
21
+ if (props.direction === "horizontal") extra = " flex-row";
22
+ return base + extra;
23
+ });
24
+ </script>
@@ -0,0 +1,25 @@
1
+ import LuStack from "./LuStack.vue";
2
+ import LuGrid from "./LuGrid.vue";
3
+ import LuDock from "./LuDock.vue";
4
+ import LuDockItem from "./LuDockItem.vue";
5
+ import LuSplit from "./LuSplit.vue";
6
+ import LuSplitPane from "./LuSplitPane.vue";
7
+ import LuSplitResizer from "./LuSplitResizer.vue";
8
+ import LuScroll from "./LuScroll.vue";
9
+ import LuFill from "./LuFill.vue";
10
+ import LuFixed from "./LuFixed.vue";
11
+ import LuOverlay from "./LuOverlay.vue";
12
+
13
+ export {
14
+ LuStack,
15
+ LuGrid,
16
+ LuDock,
17
+ LuDockItem,
18
+ LuSplit,
19
+ LuSplitPane,
20
+ LuSplitResizer,
21
+ LuScroll,
22
+ LuFill,
23
+ LuFixed,
24
+ LuOverlay,
25
+ };
package/src/plugin.ts ADDED
@@ -0,0 +1,27 @@
1
+ import type { App, Plugin } from "vue";
2
+ import { LumoraUIConfigKey } from "./context";
3
+ import type { LumoraUIConfig } from "./types";
4
+
5
+ export interface LumoraUIPluginOptions extends LumoraUIConfig {
6
+ global?: boolean;
7
+ }
8
+
9
+ export function createLumoraUI(options: LumoraUIPluginOptions = {}): Plugin {
10
+ return {
11
+ install(app: App) {
12
+ const config: LumoraUIConfig = {
13
+ target: options.target ?? "desktop",
14
+ skin: options.skin,
15
+ locale: options.locale ?? "en-US",
16
+ a11y: options.a11y ?? true,
17
+ icons: options.icons,
18
+ };
19
+
20
+ app.provide(LumoraUIConfigKey, config);
21
+
22
+ if (options.global) {
23
+ // TODO: globally register all components if requested
24
+ }
25
+ },
26
+ };
27
+ }
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <div v-bind="$attrs" :class="[resolvedSkin, isExpanded ? expandedSkin : '']" @mouseenter="onHover(true)" @mouseleave="onHover(false)">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed, inject, ref } from "vue";
9
+ import { useLumoraConfig } from "../../context";
10
+
11
+ const props = defineProps<{ variant?: string; expanded?: boolean; expandOnHover?: boolean }>();
12
+
13
+ const hovered = ref(false);
14
+ const isExpanded = computed(() => props.expanded || (props.expandOnHover && hovered.value));
15
+
16
+ const onHover = (val: boolean) => {
17
+ hovered.value = val;
18
+ };
19
+
20
+ const { resolveSkin } = useLumoraConfig();
21
+ const resolvedSkin = computed(() => resolveSkin("LuDesktopRailBar", props.variant));
22
+ const expandedSkin = computed(() => resolveSkin("LuDesktopRailBar", "expanded"));
23
+ </script>
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <div v-bind="$attrs" :class="[resolvedSkin, active ? activeSkin : '']">
3
+ <div :class="iconSkin">
4
+ <slot name="icon" />
5
+ </div>
6
+ <div v-if="$slots.default" :class="labelSkin">
7
+ <slot />
8
+ </div>
9
+ </div>
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ import { computed } from "vue";
14
+ import { useLumoraConfig } from "../../context";
15
+
16
+ const props = defineProps<{ variant?: string; active?: boolean }>();
17
+
18
+ const { resolveSkin } = useLumoraConfig();
19
+ const resolvedSkin = computed(() => resolveSkin("LuDesktopRailItem", props.variant));
20
+ const activeSkin = computed(() => resolveSkin("LuDesktopRailItem", "active"));
21
+ const iconSkin = computed(() => resolveSkin("LuDesktopRailItemIcon", props.variant) || "flex-shrink-0 flex items-center justify-center w-6 h-6");
22
+ const labelSkin = computed(() => resolveSkin("LuDesktopRailItemLabel", props.variant) || "overflow-hidden transition-all duration-200");
23
+ </script>
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <div v-bind="$attrs" :class="resolvedSkin">
3
+ <slot name="topbar" />
4
+ <div :class="contentWrapperSkin">
5
+ <slot name="rail" />
6
+ <slot name="sidebar" />
7
+ <div :class="mainContentSkin">
8
+ <slot name="content" />
9
+ </div>
10
+ </div>
11
+ <slot name="statusbar" />
12
+ </div>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ import { computed } from "vue";
17
+ import { useLumoraConfig } from "../../context";
18
+
19
+ const props = defineProps<{ variant?: string }>();
20
+
21
+ const { resolveSkin } = useLumoraConfig();
22
+ const resolvedSkin = computed(() => resolveSkin("LuDesktopShell", props.variant) || "flex flex-col h-screen w-screen overflow-hidden bg-white");
23
+ const contentWrapperSkin = computed(() => resolveSkin("LuDesktopShellContentWrapper") || "flex flex-1 overflow-hidden");
24
+ const mainContentSkin = computed(() => resolveSkin("LuDesktopShellMainContent") || "flex flex-1 flex-col overflow-hidden relative");
25
+ </script>