@astrake/lumora-ui 0.1.5 → 0.2.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 (53) hide show
  1. package/CHANGELOG.md +65 -1
  2. package/package.json +9 -1
  3. package/src/components/LuAlert.vue +33 -0
  4. package/src/components/LuBreadcrumb.vue +63 -0
  5. package/src/components/LuCard.vue +8 -1
  6. package/src/components/LuCheckbox.vue +94 -0
  7. package/src/components/LuCodeBlock.vue +168 -0
  8. package/src/components/LuForm.types.ts +24 -0
  9. package/src/components/LuForm.vue +121 -0
  10. package/src/components/LuInput.vue +57 -5
  11. package/src/components/LuMenu.vue +86 -0
  12. package/src/components/LuMenuItem.vue +37 -0
  13. package/src/components/LuModal.vue +115 -0
  14. package/src/components/LuPagination.vue +118 -0
  15. package/src/components/LuRadio.vue +55 -0
  16. package/src/components/LuRadioGroup.types.ts +10 -0
  17. package/src/components/LuRadioGroup.vue +66 -0
  18. package/src/components/LuSelect.vue +38 -6
  19. package/src/components/LuSkeleton.vue +15 -0
  20. package/src/components/LuSpinner.vue +36 -0
  21. package/src/components/LuSwitch.vue +48 -12
  22. package/src/components/LuTag.vue +35 -0
  23. package/src/components/LuTextarea.vue +62 -0
  24. package/src/components/LuThemeSelect.vue +1 -1
  25. package/src/components/LuToggleButton.vue +35 -0
  26. package/src/components/LuToggleGroup.vue +27 -0
  27. package/src/components/__tests__/LuForm.test.ts +206 -0
  28. package/src/components/index.ts +18 -0
  29. package/src/context.ts +8 -5
  30. package/src/index.ts +2 -2
  31. package/src/layout/LuDock.vue +53 -20
  32. package/src/layout/LuDockItem.vue +3 -1
  33. package/src/layout/LuFill.vue +15 -5
  34. package/src/layout/LuFixed.vue +15 -5
  35. package/src/layout/LuGrid.vue +28 -5
  36. package/src/layout/LuScroll.vue +3 -1
  37. package/src/layout/LuSplitPane.vue +3 -3
  38. package/src/layout/LuSplitResizer.vue +5 -3
  39. package/src/layout/LuStack.vue +16 -11
  40. package/src/lumora.css +16 -0
  41. package/src/plugin.ts +3 -2
  42. package/src/shell/desktop/LuDesktopRailItem.vue +2 -2
  43. package/src/shell/desktop/LuDesktopShell.vue +3 -3
  44. package/src/shell/embedded/LuEmbeddedShell.vue +2 -2
  45. package/src/shell/embedded/LuEmbeddedStatusBar.vue +16 -0
  46. package/src/shell/embedded/LuEmbeddedTopBar.vue +17 -0
  47. package/src/shell/index.ts +4 -1
  48. package/src/shell/mobile/LuMobileHeader.vue +17 -0
  49. package/src/shell/mobile/LuMobileNavBar.vue +15 -0
  50. package/src/shell/mobile/LuMobileShell.vue +2 -2
  51. package/src/skins/default.ts +361 -29
  52. package/src/tailwind.ts +25 -0
  53. package/src/utils.ts +95 -0
@@ -1,17 +1,27 @@
1
1
  <template>
2
- <div v-bind="$attrs" :class="resolvedSkin">
2
+ <component :is="as" v-bind="$attrs" :class="resolvedSkin">
3
3
  <slot />
4
- </div>
4
+ </component>
5
5
  </template>
6
6
 
7
7
  <script setup lang="ts">
8
8
  import { computed } from "vue";
9
9
  import { useLumoraConfig } from "../context";
10
+ import { resolveLayoutProps, cn } from "../utils";
10
11
 
11
- const props = defineProps<{
12
+ const props = withDefaults(defineProps<{
12
13
  variant?: string;
13
- }>();
14
+ as?: string;
15
+ width?: string;
16
+ height?: string;
17
+ padding?: string | number;
18
+ }>(), {
19
+ as: 'div'
20
+ });
14
21
 
15
22
  const { resolveSkin } = useLumoraConfig();
16
- const resolvedSkin = computed(() => resolveSkin("LuFill", props.variant));
23
+ const resolvedSkin = computed(() => cn(
24
+ resolveSkin("LuFill", props.variant),
25
+ resolveLayoutProps(props)
26
+ ));
17
27
  </script>
@@ -1,17 +1,27 @@
1
1
  <template>
2
- <div v-bind="$attrs" :class="resolvedSkin">
2
+ <component :is="as" v-bind="$attrs" :class="resolvedSkin">
3
3
  <slot />
4
- </div>
4
+ </component>
5
5
  </template>
6
6
 
7
7
  <script setup lang="ts">
8
8
  import { computed } from "vue";
9
9
  import { useLumoraConfig } from "../context";
10
+ import { resolveLayoutProps, cn } from "../utils";
10
11
 
11
- const props = defineProps<{
12
+ const props = withDefaults(defineProps<{
12
13
  variant?: string;
13
- }>();
14
+ as?: string;
15
+ width?: string;
16
+ height?: string;
17
+ padding?: string | number;
18
+ }>(), {
19
+ as: 'div'
20
+ });
14
21
 
15
22
  const { resolveSkin } = useLumoraConfig();
16
- const resolvedSkin = computed(() => resolveSkin("LuFixed", props.variant));
23
+ const resolvedSkin = computed(() => cn(
24
+ resolveSkin("LuFixed", props.variant),
25
+ resolveLayoutProps(props)
26
+ ));
17
27
  </script>
@@ -1,20 +1,43 @@
1
1
  <template>
2
- <div v-bind="$attrs" :class="resolvedSkin" :style="gridStyle">
2
+ <component :is="as" v-bind="$attrs" :class="resolvedSkin" :style="gridStyle">
3
3
  <slot />
4
- </div>
4
+ </component>
5
5
  </template>
6
6
 
7
7
  <script setup lang="ts">
8
8
  import { computed } from "vue";
9
9
  import { useLumoraConfig } from "../context";
10
+ import { resolveLayoutProps, cn } from "../utils";
10
11
 
11
- const props = defineProps<{
12
+ const props = withDefaults(defineProps<{
12
13
  cols?: number;
14
+ smCols?: number;
15
+ mdCols?: number;
16
+ lgCols?: number;
13
17
  variant?: string;
14
- }>();
18
+ as?: string;
19
+ gap?: string | number;
20
+ padding?: string | number;
21
+ align?: 'start' | 'center' | 'end' | 'stretch' | 'baseline';
22
+ justify?: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly';
23
+ }>(), {
24
+ as: 'div'
25
+ });
26
+
27
+ const colsClass = computed(() => {
28
+ const map: Record<number, string> = {1:'grid-cols-1',2:'grid-cols-2',3:'grid-cols-3',4:'grid-cols-4',5:'grid-cols-5',6:'grid-cols-6',12:'grid-cols-12'};
29
+ const sm = props.smCols && map[props.smCols] ? `sm:${map[props.smCols]}` : '';
30
+ const md = props.mdCols && map[props.mdCols] ? `md:${map[props.mdCols]}` : '';
31
+ const lg = props.lgCols && map[props.lgCols] ? `lg:${map[props.lgCols]}` : '';
32
+ return [sm, md, lg].filter(Boolean).join(' ');
33
+ });
15
34
 
16
35
  const { resolveSkin } = useLumoraConfig();
17
- const resolvedSkin = computed(() => resolveSkin("LuGrid", props.variant) || "grid");
36
+ const resolvedSkin = computed(() => cn(
37
+ resolveSkin("LuGrid", props.variant),
38
+ resolveLayoutProps(props),
39
+ colsClass.value
40
+ ));
18
41
 
19
42
  const gridStyle = computed(() =>
20
43
  props.cols ? { gridTemplateColumns: `repeat(${props.cols}, minmax(0, 1fr))` } : {}
@@ -13,5 +13,7 @@ const props = defineProps<{
13
13
  }>();
14
14
 
15
15
  const { resolveSkin } = useLumoraConfig();
16
- const resolvedSkin = computed(() => resolveSkin("LuScroll", props.variant));
16
+ const resolvedSkin = computed(() => [
17
+ resolveSkin("LuScroll", props.variant)
18
+ ]);
17
19
  </script>
@@ -5,7 +5,7 @@
5
5
  </template>
6
6
 
7
7
  <script setup lang="ts">
8
- import { computed, inject, ref } from "vue";
8
+ import { computed, inject, ref, unref, type Ref } from "vue";
9
9
  import { useLumoraConfig } from "../context";
10
10
 
11
11
  const props = defineProps<{
@@ -15,13 +15,13 @@ const props = defineProps<{
15
15
  variant?: string;
16
16
  }>();
17
17
 
18
- const direction = inject<"horizontal" | "vertical">("lu-split-direction", "horizontal");
18
+ const direction = inject<Ref<"horizontal" | "vertical"> | "horizontal">("lu-split-direction", "horizontal");
19
19
  const size = ref(props.defaultSize);
20
20
 
21
21
  const style = computed(() => {
22
22
  if (size.value === undefined) return { flex: "1 1 0%" };
23
23
  const dim = `${size.value}px`;
24
- return direction === "horizontal"
24
+ return unref(direction) === "horizontal"
25
25
  ? { width: dim, flex: `0 0 ${dim}` }
26
26
  : { height: dim, flex: `0 0 ${dim}` };
27
27
  });
@@ -3,15 +3,17 @@
3
3
  </template>
4
4
 
5
5
  <script setup lang="ts">
6
- import { computed, inject } from "vue";
6
+ import { computed, inject, unref, type Ref } from "vue";
7
7
  import { useLumoraConfig } from "../context";
8
8
 
9
9
  const props = defineProps<{
10
10
  variant?: string;
11
11
  }>();
12
12
 
13
- const direction = inject<"horizontal" | "vertical">("lu-split-direction", "horizontal");
13
+ const direction = inject<Ref<"horizontal" | "vertical"> | "horizontal">("lu-split-direction", "horizontal");
14
14
 
15
15
  const { resolveSkin } = useLumoraConfig();
16
- const resolvedSkin = computed(() => resolveSkin("LuSplitResizer", direction === "horizontal" ? "horizontal" : "vertical"));
16
+ const resolvedSkin = computed(() => [
17
+ resolveSkin("LuSplitResizer", unref(direction) === "horizontal" ? "horizontal" : "vertical")
18
+ ]);
17
19
  </script>
@@ -1,24 +1,29 @@
1
1
  <template>
2
- <div v-bind="$attrs" :class="resolvedSkin">
2
+ <component :is="as" v-bind="$attrs" :class="resolvedSkin">
3
3
  <slot />
4
- </div>
4
+ </component>
5
5
  </template>
6
6
 
7
7
  <script setup lang="ts">
8
8
  import { computed } from "vue";
9
9
  import { useLumoraConfig } from "../context";
10
+ import { resolveLayoutProps, cn } from "../utils";
10
11
 
11
- const props = defineProps<{
12
+ const props = withDefaults(defineProps<{
12
13
  direction?: "vertical" | "horizontal";
13
14
  variant?: string;
14
- }>();
15
+ as?: string;
16
+ gap?: string | number;
17
+ padding?: string | number;
18
+ align?: 'start' | 'center' | 'end' | 'stretch' | 'baseline';
19
+ justify?: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly';
20
+ }>(), {
21
+ as: 'div'
22
+ });
15
23
 
16
24
  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
- });
25
+ const resolvedSkin = computed(() => cn(
26
+ resolveSkin("LuStack", props.direction ?? props.variant),
27
+ resolveLayoutProps(props)
28
+ ));
24
29
  </script>
package/src/lumora.css ADDED
@@ -0,0 +1,16 @@
1
+ /* LumoraUI structural baseline — Tailwind v4 Escape Hatch, v0.1.7+ */
2
+ /* Consumers MUST import this: import '@astrake/lumora-ui/style' */
3
+
4
+ /*
5
+ With the transition to Tailwind v4 as the native design engine,
6
+ all structural layout classes (e.g., .lu-button, .lu-stack) have been removed.
7
+ Layout structure is now provided by the defaultSkin (`default.ts`) out of the box.
8
+
9
+ This file serves as an escape hatch for:
10
+ 1. Complex [data-state="..."] selector combinations.
11
+ 2. Webkit scrollbar pseudoclasses.
12
+ 3. Keyframe animations that Tailwind v4 cannot natively express.
13
+ */
14
+
15
+ /* Example: Webkit Scrollbar overrides can go here */
16
+
package/src/plugin.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { App, Plugin } from "vue";
2
+ import { shallowReactive } from "vue";
2
3
  import { LumoraUIConfigKey } from "./context";
3
4
  import type { LumoraUIConfig } from "./types";
4
5
 
@@ -9,13 +10,13 @@ export interface LumoraUIPluginOptions extends LumoraUIConfig {
9
10
  export function createLumoraUI(options: LumoraUIPluginOptions = {}): Plugin {
10
11
  return {
11
12
  install(app: App) {
12
- const config: LumoraUIConfig = {
13
+ const config = shallowReactive<LumoraUIConfig>({
13
14
  target: options.target ?? "desktop",
14
15
  skin: options.skin,
15
16
  locale: options.locale ?? "en-US",
16
17
  a11y: options.a11y ?? true,
17
18
  icons: options.icons,
18
- };
19
+ });
19
20
 
20
21
  app.provide(LumoraUIConfigKey, config);
21
22
 
@@ -18,6 +18,6 @@ const props = defineProps<{ variant?: string; active?: boolean }>();
18
18
  const { resolveSkin } = useLumoraConfig();
19
19
  const resolvedSkin = computed(() => resolveSkin("LuDesktopRailItem", props.variant));
20
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");
21
+ const iconSkin = computed(() => resolveSkin("LuDesktopRailItemIcon", props.variant));
22
+ const labelSkin = computed(() => resolveSkin("LuDesktopRailItemLabel", props.variant));
23
23
  </script>
@@ -19,7 +19,7 @@ import { useLumoraConfig } from "../../context";
19
19
  const props = defineProps<{ variant?: string }>();
20
20
 
21
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");
22
+ const resolvedSkin = computed(() => resolveSkin("LuDesktopShell", props.variant));
23
+ const contentWrapperSkin = computed(() => resolveSkin("LuDesktopShellContentWrapper"));
24
+ const mainContentSkin = computed(() => resolveSkin("LuDesktopShellMainContent"));
25
25
  </script>
@@ -15,6 +15,6 @@ import { useLumoraConfig } from "../../context";
15
15
  const props = defineProps<{ variant?: string }>();
16
16
 
17
17
  const { resolveSkin } = useLumoraConfig();
18
- const resolvedSkin = computed(() => resolveSkin("LuEmbeddedShell", props.variant) || "flex flex-col h-screen w-screen overflow-hidden bg-black text-white touch-none select-none");
19
- const contentSkin = computed(() => resolveSkin("LuEmbeddedShellContent", props.variant) || "flex-1 min-h-0 min-w-0 flex flex-col relative overflow-hidden");
18
+ const resolvedSkin = computed(() => resolveSkin("LuEmbeddedShell", props.variant));
19
+ const contentSkin = computed(() => resolveSkin("LuEmbeddedShellContent", props.variant));
20
20
  </script>
@@ -0,0 +1,16 @@
1
+ <template>
2
+ <div v-bind="$attrs" :class="resolvedSkin">
3
+ <slot name="left" />
4
+ <slot name="right" />
5
+ </div>
6
+ </template>
7
+
8
+ <script setup lang="ts">
9
+ import { computed } from "vue";
10
+ import { useLumoraConfig } from "../../context";
11
+
12
+ const props = defineProps<{ variant?: string }>();
13
+
14
+ const { resolveSkin } = useLumoraConfig();
15
+ const resolvedSkin = computed(() => resolveSkin("LuEmbeddedStatusBar", props.variant));
16
+ </script>
@@ -0,0 +1,17 @@
1
+ <template>
2
+ <div v-bind="$attrs" :class="resolvedSkin">
3
+ <slot name="left" />
4
+ <slot />
5
+ <slot name="right" />
6
+ </div>
7
+ </template>
8
+
9
+ <script setup lang="ts">
10
+ import { computed } from "vue";
11
+ import { useLumoraConfig } from "../../context";
12
+
13
+ const props = defineProps<{ variant?: string }>();
14
+
15
+ const { resolveSkin } = useLumoraConfig();
16
+ const resolvedSkin = computed(() => resolveSkin("LuEmbeddedTopBar", props.variant));
17
+ </script>
@@ -6,5 +6,8 @@ export { default as LuDesktopSidebar } from "./desktop/LuDesktopSidebar.vue";
6
6
  export { default as LuDesktopStatusBar } from "./desktop/LuDesktopStatusBar.vue";
7
7
 
8
8
  export { default as LuMobileShell } from "./mobile/LuMobileShell.vue";
9
-
9
+ export { default as LuMobileHeader } from "./mobile/LuMobileHeader.vue";
10
+ export { default as LuMobileNavBar } from "./mobile/LuMobileNavBar.vue";
10
11
  export { default as LuEmbeddedShell } from "./embedded/LuEmbeddedShell.vue";
12
+ export { default as LuEmbeddedTopBar } from "./embedded/LuEmbeddedTopBar.vue";
13
+ export { default as LuEmbeddedStatusBar } from "./embedded/LuEmbeddedStatusBar.vue";
@@ -0,0 +1,17 @@
1
+ <template>
2
+ <div v-bind="$attrs" :class="resolvedSkin">
3
+ <slot name="left" />
4
+ <slot />
5
+ <slot name="right" />
6
+ </div>
7
+ </template>
8
+
9
+ <script setup lang="ts">
10
+ import { computed } from "vue";
11
+ import { useLumoraConfig } from "../../context";
12
+
13
+ const props = defineProps<{ variant?: string }>();
14
+
15
+ const { resolveSkin } = useLumoraConfig();
16
+ const resolvedSkin = computed(() => resolveSkin("LuMobileHeader", props.variant));
17
+ </script>
@@ -0,0 +1,15 @@
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<{ variant?: string }>();
12
+
13
+ const { resolveSkin } = useLumoraConfig();
14
+ const resolvedSkin = computed(() => resolveSkin("LuMobileNavBar", props.variant));
15
+ </script>
@@ -16,6 +16,6 @@ import { useLumoraConfig } from "../../context";
16
16
  const props = defineProps<{ variant?: string }>();
17
17
 
18
18
  const { resolveSkin } = useLumoraConfig();
19
- const resolvedSkin = computed(() => resolveSkin("LuMobileShell", props.variant) || "flex flex-col h-full w-full overflow-hidden bg-white relative");
20
- const contentSkin = computed(() => resolveSkin("LuMobileShellContent") || "flex flex-1 flex-col overflow-y-auto relative");
19
+ const resolvedSkin = computed(() => resolveSkin("LuMobileShell", props.variant));
20
+ const contentSkin = computed(() => resolveSkin("LuMobileShellContent"));
21
21
  </script>