@astrake/lumora-ui 0.2.0 → 0.2.1
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/CHANGELOG.md +141 -137
- package/dist/LuCodeBlock.vue_vue_type_script_setup_true_lang-BjwcjuXF.js +1623 -0
- package/dist/LuEmbeddedStatusBar.vue_vue_type_script_setup_true_lang-CIksvebU.js +218 -0
- package/dist/LuOverlay.vue_vue_type_script_setup_true_lang-DZch4Vrw.js +226 -0
- package/dist/components/LuAlert.vue.d.ts +20 -0
- package/dist/components/LuAvatar.vue.d.ts +19 -0
- package/dist/components/LuBadge.vue.d.ts +16 -0
- package/dist/components/LuBreadcrumb.vue.d.ts +16 -0
- package/dist/components/LuButton.vue.d.ts +24 -0
- package/dist/components/LuCard.vue.d.ts +21 -0
- package/dist/components/LuCheckbox.vue.d.ts +27 -0
- package/dist/components/LuCodeBlock.vue.d.ts +29 -0
- package/dist/components/LuCollapsible.vue.d.ts +25 -0
- package/dist/components/LuDivider.vue.d.ts +8 -0
- package/dist/components/LuForm.types.d.ts +18 -0
- package/dist/components/LuForm.vue.d.ts +55 -0
- package/dist/components/LuIcon.vue.d.ts +18 -0
- package/dist/components/LuInput.vue.d.ts +26 -0
- package/dist/components/LuLink.vue.d.ts +23 -0
- package/dist/components/LuMenu.vue.d.ts +26 -0
- package/dist/components/LuMenuItem.vue.d.ts +20 -0
- package/dist/components/LuModal.vue.d.ts +32 -0
- package/dist/components/LuPageHeader.vue.d.ts +10 -0
- package/dist/components/LuPagination.vue.d.ts +18 -0
- package/dist/components/LuProgressBar.vue.d.ts +7 -0
- package/dist/components/LuRadio.vue.d.ts +18 -0
- package/dist/components/LuRadioGroup.types.d.ts +8 -0
- package/dist/components/LuRadioGroup.vue.d.ts +24 -0
- package/dist/components/LuSelect.vue.d.ts +18 -0
- package/dist/components/LuSkeleton.vue.d.ts +5 -0
- package/dist/components/LuSpinner.vue.d.ts +5 -0
- package/dist/components/LuSwitch.vue.d.ts +15 -0
- package/dist/components/LuTab.vue.d.ts +16 -0
- package/dist/components/LuTabList.vue.d.ts +15 -0
- package/dist/components/LuTabPanel.vue.d.ts +16 -0
- package/dist/components/LuTable.vue.d.ts +15 -0
- package/dist/components/LuTableBody.vue.d.ts +15 -0
- package/dist/components/LuTableCell.vue.d.ts +15 -0
- package/dist/components/LuTableHead.vue.d.ts +15 -0
- package/dist/components/LuTableHeadCell.vue.d.ts +15 -0
- package/dist/components/LuTableRow.vue.d.ts +15 -0
- package/dist/components/LuTabs.vue.d.ts +20 -0
- package/dist/components/LuTag.vue.d.ts +20 -0
- package/dist/components/LuText.vue.d.ts +16 -0
- package/dist/components/LuTextarea.vue.d.ts +14 -0
- package/dist/components/LuThemeSelect.vue.d.ts +2 -0
- package/dist/components/LuThemeSwitch.vue.d.ts +2 -0
- package/dist/components/LuToggleButton.vue.d.ts +16 -0
- package/dist/components/LuToggleGroup.vue.d.ts +20 -0
- package/dist/components/LuTooltip.vue.d.ts +19 -0
- package/dist/components/__tests__/LuForm.test.d.ts +1 -0
- package/dist/components/index.js +47 -0
- package/{src/composables/index.ts → dist/composables/index.d.ts} +0 -1
- package/dist/composables/index.js +30 -0
- package/dist/composables/useRail.d.ts +6 -0
- package/dist/composables/useSplit.d.ts +4 -0
- package/dist/composables/useTheme.d.ts +7 -0
- package/dist/context-0gENwESP.js +62 -0
- package/dist/context.d.ts +8 -0
- package/{src/index.ts → dist/index.d.ts} +0 -1
- package/dist/index.js +94 -0
- package/dist/layout/LuDock.vue.d.ts +9 -0
- package/dist/layout/LuDockItem.vue.d.ts +16 -0
- package/dist/layout/LuFill.vue.d.ts +21 -0
- package/dist/layout/LuFixed.vue.d.ts +21 -0
- package/dist/layout/LuGrid.vue.d.ts +26 -0
- package/dist/layout/LuOverlay.vue.d.ts +15 -0
- package/dist/layout/LuScroll.vue.d.ts +15 -0
- package/dist/layout/LuSplit.vue.d.ts +16 -0
- package/dist/layout/LuSplitPane.vue.d.ts +18 -0
- package/dist/layout/LuSplitResizer.vue.d.ts +5 -0
- package/dist/layout/LuStack.vue.d.ts +23 -0
- package/{src/layout/index.ts → dist/layout/index.d.ts} +1 -14
- package/dist/layout/index.js +14 -0
- package/dist/plugin.d.ts +6 -0
- package/dist/shell/desktop/LuDesktopRailBar.vue.d.ts +17 -0
- package/dist/shell/desktop/LuDesktopRailItem.vue.d.ts +18 -0
- package/dist/shell/desktop/LuDesktopShell.vue.d.ts +23 -0
- package/dist/shell/desktop/LuDesktopSidebar.vue.d.ts +21 -0
- package/dist/shell/desktop/LuDesktopStatusBar.vue.d.ts +15 -0
- package/dist/shell/desktop/LuDesktopTopBar.vue.d.ts +15 -0
- package/dist/shell/embedded/LuEmbeddedShell.vue.d.ts +19 -0
- package/dist/shell/embedded/LuEmbeddedStatusBar.vue.d.ts +17 -0
- package/dist/shell/embedded/LuEmbeddedTopBar.vue.d.ts +19 -0
- package/{src/shell/index.ts → dist/shell/index.d.ts} +0 -1
- package/dist/shell/index.js +15 -0
- package/dist/shell/mobile/LuMobileHeader.vue.d.ts +19 -0
- package/dist/shell/mobile/LuMobileNavBar.vue.d.ts +15 -0
- package/dist/shell/mobile/LuMobileShell.vue.d.ts +21 -0
- package/dist/skins/default.d.ts +2 -0
- package/{src/skins/default.ts → dist/skins/index.js} +119 -129
- package/dist/tailwind.d.ts +1 -0
- package/dist/tailwind.js +13 -0
- package/dist/types.d.ts +15 -0
- package/dist/useTheme-Cd4wVaLs.js +21 -0
- package/dist/utils.d.ts +21 -0
- package/package.json +94 -69
- package/src/lumora.css +16 -16
- package/src/components/LuAlert.vue +0 -33
- package/src/components/LuAvatar.vue +0 -22
- package/src/components/LuBadge.vue +0 -15
- package/src/components/LuBreadcrumb.vue +0 -63
- package/src/components/LuButton.vue +0 -58
- package/src/components/LuCard.vue +0 -27
- package/src/components/LuCheckbox.vue +0 -94
- package/src/components/LuCodeBlock.vue +0 -168
- package/src/components/LuCollapsible.vue +0 -34
- package/src/components/LuDivider.vue +0 -18
- package/src/components/LuForm.types.ts +0 -24
- package/src/components/LuForm.vue +0 -121
- package/src/components/LuIcon.vue +0 -39
- package/src/components/LuInput.vue +0 -82
- package/src/components/LuLink.vue +0 -47
- package/src/components/LuMenu.vue +0 -86
- package/src/components/LuMenuItem.vue +0 -37
- package/src/components/LuModal.vue +0 -115
- package/src/components/LuPageHeader.vue +0 -24
- package/src/components/LuPagination.vue +0 -118
- package/src/components/LuProgressBar.vue +0 -21
- package/src/components/LuRadio.vue +0 -55
- package/src/components/LuRadioGroup.types.ts +0 -10
- package/src/components/LuRadioGroup.vue +0 -66
- package/src/components/LuSelect.vue +0 -67
- package/src/components/LuSkeleton.vue +0 -15
- package/src/components/LuSpinner.vue +0 -36
- package/src/components/LuSwitch.vue +0 -76
- package/src/components/LuTab.vue +0 -26
- package/src/components/LuTabList.vue +0 -15
- package/src/components/LuTabPanel.vue +0 -19
- package/src/components/LuTable.vue +0 -15
- package/src/components/LuTableBody.vue +0 -15
- package/src/components/LuTableCell.vue +0 -15
- package/src/components/LuTableHead.vue +0 -15
- package/src/components/LuTableHeadCell.vue +0 -15
- package/src/components/LuTableRow.vue +0 -15
- package/src/components/LuTabs.vue +0 -30
- package/src/components/LuTag.vue +0 -35
- package/src/components/LuText.vue +0 -18
- package/src/components/LuTextarea.vue +0 -62
- package/src/components/LuThemeSelect.vue +0 -26
- package/src/components/LuThemeSwitch.vue +0 -22
- package/src/components/LuToggleButton.vue +0 -35
- package/src/components/LuToggleGroup.vue +0 -27
- package/src/components/LuTooltip.vue +0 -36
- package/src/components/__tests__/LuForm.test.ts +0 -206
- package/src/composables/useRail.ts +0 -24
- package/src/composables/useSplit.ts +0 -17
- package/src/composables/useTheme.ts +0 -36
- package/src/context.ts +0 -39
- package/src/layout/LuDock.vue +0 -56
- package/src/layout/LuDockItem.vue +0 -20
- package/src/layout/LuFill.vue +0 -27
- package/src/layout/LuFixed.vue +0 -27
- package/src/layout/LuGrid.vue +0 -45
- package/src/layout/LuOverlay.vue +0 -17
- package/src/layout/LuScroll.vue +0 -19
- package/src/layout/LuSplit.vue +0 -23
- package/src/layout/LuSplitPane.vue +0 -32
- package/src/layout/LuSplitResizer.vue +0 -19
- package/src/layout/LuStack.vue +0 -29
- package/src/plugin.ts +0 -28
- package/src/shell/desktop/LuDesktopRailBar.vue +0 -23
- package/src/shell/desktop/LuDesktopRailItem.vue +0 -23
- package/src/shell/desktop/LuDesktopShell.vue +0 -25
- package/src/shell/desktop/LuDesktopSidebar.vue +0 -36
- package/src/shell/desktop/LuDesktopStatusBar.vue +0 -15
- package/src/shell/desktop/LuDesktopTopBar.vue +0 -15
- package/src/shell/embedded/LuEmbeddedShell.vue +0 -20
- package/src/shell/embedded/LuEmbeddedStatusBar.vue +0 -16
- package/src/shell/embedded/LuEmbeddedTopBar.vue +0 -17
- package/src/shell/mobile/LuMobileHeader.vue +0 -17
- package/src/shell/mobile/LuMobileNavBar.vue +0 -15
- package/src/shell/mobile/LuMobileShell.vue +0 -21
- package/src/tailwind.ts +0 -25
- package/src/types.ts +0 -18
- package/src/utils.ts +0 -95
- package/tsconfig.json +0 -10
- /package/{src/components/index.ts → dist/components/index.d.ts} +0 -0
- /package/{src/skins/index.ts → dist/skins/index.d.ts} +0 -0
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div v-bind="$attrs" :class="resolvedSkin.container">
|
|
3
|
-
<div v-if="title || description" :class="resolvedSkin.header">
|
|
4
|
-
<h3 v-if="title" :class="resolvedSkin.title">{{ title }}</h3>
|
|
5
|
-
<p v-if="description" :class="resolvedSkin.description">{{ description }}</p>
|
|
6
|
-
</div>
|
|
7
|
-
|
|
8
|
-
<template v-if="variant === 'preview'">
|
|
9
|
-
<div :class="resolvedSkin.card">
|
|
10
|
-
<template v-if="layout === 'tabbed'">
|
|
11
|
-
<LuTabs v-model="activeTab" variant="default">
|
|
12
|
-
<LuTabList variant="card-header">
|
|
13
|
-
<LuTab value="preview">Preview</LuTab>
|
|
14
|
-
<LuTab value="code">Code</LuTab>
|
|
15
|
-
</LuTabList>
|
|
16
|
-
</LuTabs>
|
|
17
|
-
|
|
18
|
-
<div v-if="activeTab === 'preview'" :class="resolvedSkin.previewArea">
|
|
19
|
-
<slot name="preview" />
|
|
20
|
-
</div>
|
|
21
|
-
|
|
22
|
-
<div v-if="activeTab === 'code'" :class="resolvedSkin.codeArea">
|
|
23
|
-
<div :class="resolvedSkin.codeHeader">
|
|
24
|
-
<div :class="resolvedSkin.badge">{{ lang }}</div>
|
|
25
|
-
<button @click="copyCode" :class="resolvedSkin.copyButton" aria-label="Copy code">
|
|
26
|
-
<LuIcon :name="copied ? 'check' : 'copy'" class="w-4 h-4" />
|
|
27
|
-
</button>
|
|
28
|
-
</div>
|
|
29
|
-
<div :class="resolvedSkin.codeContent" v-html="html"></div>
|
|
30
|
-
</div>
|
|
31
|
-
</template>
|
|
32
|
-
|
|
33
|
-
<template v-else-if="layout === 'split'">
|
|
34
|
-
<div :class="resolvedSkin.splitContainer">
|
|
35
|
-
<div :class="resolvedSkin.previewArea">
|
|
36
|
-
<slot name="preview" />
|
|
37
|
-
</div>
|
|
38
|
-
<div :class="resolvedSkin.splitCodeArea">
|
|
39
|
-
<div :class="resolvedSkin.codeHeader">
|
|
40
|
-
<div :class="resolvedSkin.badge">{{ lang }}</div>
|
|
41
|
-
<button @click="copyCode" :class="resolvedSkin.copyButton" aria-label="Copy code">
|
|
42
|
-
<LuIcon :name="copied ? 'check' : 'copy'" class="w-4 h-4" />
|
|
43
|
-
</button>
|
|
44
|
-
</div>
|
|
45
|
-
<div :class="resolvedSkin.codeContent" v-html="html"></div>
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
</template>
|
|
49
|
-
</div>
|
|
50
|
-
</template>
|
|
51
|
-
|
|
52
|
-
<template v-else>
|
|
53
|
-
<div :class="resolvedSkin.card">
|
|
54
|
-
<div :class="resolvedSkin.codeArea">
|
|
55
|
-
<div :class="resolvedSkin.codeHeader">
|
|
56
|
-
<div :class="resolvedSkin.badge">{{ lang }}</div>
|
|
57
|
-
<button @click="copyCode" :class="resolvedSkin.copyButton" aria-label="Copy code">
|
|
58
|
-
<LuIcon :name="copied ? 'check' : 'copy'" class="w-4 h-4" />
|
|
59
|
-
</button>
|
|
60
|
-
</div>
|
|
61
|
-
<div :class="resolvedSkin.codeContent" v-html="html"></div>
|
|
62
|
-
</div>
|
|
63
|
-
</div>
|
|
64
|
-
</template>
|
|
65
|
-
</div>
|
|
66
|
-
</template>
|
|
67
|
-
|
|
68
|
-
<script lang="ts">
|
|
69
|
-
import { createHighlighter, type Highlighter } from 'shiki';
|
|
70
|
-
|
|
71
|
-
let globalHighlighter: Highlighter | null = null;
|
|
72
|
-
let highlighterPromise: Promise<Highlighter> | null = null;
|
|
73
|
-
|
|
74
|
-
async function getHighlighter() {
|
|
75
|
-
if (globalHighlighter) return globalHighlighter;
|
|
76
|
-
if (!highlighterPromise) {
|
|
77
|
-
highlighterPromise = createHighlighter({
|
|
78
|
-
themes: ['one-dark-pro'],
|
|
79
|
-
langs: ['bash', 'vue', 'ts', 'html', 'css', 'json']
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
globalHighlighter = await highlighterPromise;
|
|
83
|
-
return globalHighlighter;
|
|
84
|
-
}
|
|
85
|
-
</script>
|
|
86
|
-
|
|
87
|
-
<script setup lang="ts">
|
|
88
|
-
import { ref, watch, onMounted, computed } from "vue";
|
|
89
|
-
import { useLumoraConfig } from "../context";
|
|
90
|
-
import LuTabs from "./LuTabs.vue";
|
|
91
|
-
import LuTabList from "./LuTabList.vue";
|
|
92
|
-
import LuTab from "./LuTab.vue";
|
|
93
|
-
import LuIcon from "./LuIcon.vue";
|
|
94
|
-
|
|
95
|
-
const props = withDefaults(defineProps<{
|
|
96
|
-
code: string;
|
|
97
|
-
lang?: string;
|
|
98
|
-
variant?: "default" | "preview";
|
|
99
|
-
layout?: "tabbed" | "split";
|
|
100
|
-
title?: string;
|
|
101
|
-
description?: string;
|
|
102
|
-
}>(), {
|
|
103
|
-
lang: "vue",
|
|
104
|
-
variant: "default",
|
|
105
|
-
layout: "tabbed"
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
const { resolveSkin } = useLumoraConfig();
|
|
109
|
-
const resolvedSkin = computed(() => {
|
|
110
|
-
return {
|
|
111
|
-
container: resolveSkin("LuCodeBlock", "container"),
|
|
112
|
-
header: resolveSkin("LuCodeBlock", "header"),
|
|
113
|
-
title: resolveSkin("LuCodeBlock", "title"),
|
|
114
|
-
description: resolveSkin("LuCodeBlock", "description"),
|
|
115
|
-
card: resolveSkin("LuCodeBlock", "card"),
|
|
116
|
-
previewArea: resolveSkin("LuCodeBlock", "previewArea"),
|
|
117
|
-
codeArea: resolveSkin("LuCodeBlock", "codeArea"),
|
|
118
|
-
splitCodeArea: resolveSkin("LuCodeBlock", "splitCodeArea"),
|
|
119
|
-
splitContainer: resolveSkin("LuCodeBlock", "splitContainer"),
|
|
120
|
-
codeHeader: resolveSkin("LuCodeBlock", "codeHeader"),
|
|
121
|
-
badge: resolveSkin("LuCodeBlock", "badge"),
|
|
122
|
-
copyButton: resolveSkin("LuCodeBlock", "copyButton"),
|
|
123
|
-
codeContent: resolveSkin("LuCodeBlock", "codeContent"),
|
|
124
|
-
};
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
const html = ref('');
|
|
128
|
-
const activeTab = ref<'preview' | 'code'>('preview');
|
|
129
|
-
const copied = ref(false);
|
|
130
|
-
|
|
131
|
-
const highlight = async () => {
|
|
132
|
-
if (!props.code) {
|
|
133
|
-
html.value = '';
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
try {
|
|
138
|
-
const highlighter = await getHighlighter();
|
|
139
|
-
|
|
140
|
-
html.value = highlighter.codeToHtml(props.code, {
|
|
141
|
-
lang: props.lang || 'vue',
|
|
142
|
-
theme: 'one-dark-pro'
|
|
143
|
-
});
|
|
144
|
-
} catch (e) {
|
|
145
|
-
console.error('Failed to highlight code', e);
|
|
146
|
-
// fallback to plain pre
|
|
147
|
-
html.value = `<pre><code>${props.code.replace(/</g, '<').replace(/>/g, '>')}</code></pre>`;
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
onMounted(() => {
|
|
152
|
-
highlight();
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
watch(() => [props.code, props.lang], () => {
|
|
156
|
-
highlight();
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
const copyCode = async () => {
|
|
160
|
-
try {
|
|
161
|
-
await navigator.clipboard.writeText(props.code);
|
|
162
|
-
copied.value = true;
|
|
163
|
-
setTimeout(() => copied.value = false, 2000);
|
|
164
|
-
} catch (err) {
|
|
165
|
-
console.error('Failed to copy', err);
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
</script>
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div v-bind="$attrs" :class="wrapperSkin">
|
|
3
|
-
<button :class="triggerSkin" @click="toggle">
|
|
4
|
-
<slot name="trigger" :isOpen="isOpen" />
|
|
5
|
-
</button>
|
|
6
|
-
<div v-show="isOpen" :class="contentSkin">
|
|
7
|
-
<slot name="content" />
|
|
8
|
-
</div>
|
|
9
|
-
</div>
|
|
10
|
-
</template>
|
|
11
|
-
|
|
12
|
-
<script setup lang="ts">
|
|
13
|
-
import { computed, ref, watch } from "vue";
|
|
14
|
-
import { useLumoraConfig } from "../context";
|
|
15
|
-
|
|
16
|
-
const props = defineProps<{ variant?: string; modelValue?: boolean; defaultOpen?: boolean }>();
|
|
17
|
-
const emit = defineEmits<{ (e: "update:modelValue", val: boolean): void }>();
|
|
18
|
-
|
|
19
|
-
const isOpen = ref(props.modelValue ?? props.defaultOpen ?? false);
|
|
20
|
-
|
|
21
|
-
watch(() => props.modelValue, (val) => {
|
|
22
|
-
if (val !== undefined) isOpen.value = val;
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const toggle = () => {
|
|
26
|
-
isOpen.value = !isOpen.value;
|
|
27
|
-
emit("update:modelValue", isOpen.value);
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const { resolveSkin } = useLumoraConfig();
|
|
31
|
-
const wrapperSkin = computed(() => resolveSkin("LuCollapsible", props.variant));
|
|
32
|
-
const triggerSkin = computed(() => resolveSkin("LuCollapsibleTrigger", props.variant));
|
|
33
|
-
const contentSkin = computed(() => resolveSkin("LuCollapsibleContent", props.variant));
|
|
34
|
-
</script>
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div v-bind="$attrs" :class="resolvedSkin"></div>
|
|
3
|
-
</template>
|
|
4
|
-
|
|
5
|
-
<script setup lang="ts">
|
|
6
|
-
import { computed } from "vue";
|
|
7
|
-
import { useLumoraConfig } from "../context";
|
|
8
|
-
|
|
9
|
-
const props = withDefaults(defineProps<{
|
|
10
|
-
variant?: string;
|
|
11
|
-
orientation?: "horizontal" | "vertical";
|
|
12
|
-
}>(), {
|
|
13
|
-
orientation: "horizontal"
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
const { resolveSkin } = useLumoraConfig();
|
|
17
|
-
const resolvedSkin = computed(() => resolveSkin("LuDivider", props.variant || props.orientation));
|
|
18
|
-
</script>
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { InjectionKey, Ref } from "vue";
|
|
2
|
-
|
|
3
|
-
export type LuFormValidator = (value: unknown) => string | null | Promise<string | null>;
|
|
4
|
-
|
|
5
|
-
export type LuFormRules = Record<string, LuFormValidator | LuFormValidator[]>;
|
|
6
|
-
|
|
7
|
-
export type LuFormErrors = Record<string, string>;
|
|
8
|
-
|
|
9
|
-
export interface LuFormFieldRegistration {
|
|
10
|
-
name: string;
|
|
11
|
-
getValue: () => unknown;
|
|
12
|
-
setValue: (v: unknown) => void;
|
|
13
|
-
setError: (msg: string | null) => void;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const LuFormContextKey = Symbol("LuFormContext") as InjectionKey<LuFormContext>;
|
|
17
|
-
|
|
18
|
-
export interface LuFormContext {
|
|
19
|
-
register(field: LuFormFieldRegistration): void;
|
|
20
|
-
unregister(name: string): void;
|
|
21
|
-
getError(name: string): string | null;
|
|
22
|
-
validateOn: Readonly<Ref<"submit" | "blur" | "both">>;
|
|
23
|
-
disabled: Readonly<Ref<boolean>>;
|
|
24
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<form @submit.prevent="handleSubmit" @reset.prevent="handleReset">
|
|
3
|
-
<slot />
|
|
4
|
-
<slot
|
|
5
|
-
name="errors"
|
|
6
|
-
:errors="errors"
|
|
7
|
-
:has-errors="hasErrors"
|
|
8
|
-
/>
|
|
9
|
-
<slot
|
|
10
|
-
name="actions"
|
|
11
|
-
:submit="handleSubmit"
|
|
12
|
-
:reset="handleReset"
|
|
13
|
-
:pending="pending"
|
|
14
|
-
/>
|
|
15
|
-
</form>
|
|
16
|
-
</template>
|
|
17
|
-
|
|
18
|
-
<script setup lang="ts">
|
|
19
|
-
import { ref, computed, provide, readonly } from "vue";
|
|
20
|
-
import type { LuFormRules, LuFormErrors, LuFormFieldRegistration, LuFormContext } from "./LuForm.types";
|
|
21
|
-
import { LuFormContextKey } from "./LuForm.types";
|
|
22
|
-
|
|
23
|
-
const props = withDefaults(defineProps<{
|
|
24
|
-
rules?: LuFormRules;
|
|
25
|
-
validateOn?: "submit" | "blur" | "both";
|
|
26
|
-
resetOnSubmit?: boolean;
|
|
27
|
-
disabled?: boolean;
|
|
28
|
-
}>(), {
|
|
29
|
-
rules: () => ({}),
|
|
30
|
-
validateOn: "submit",
|
|
31
|
-
resetOnSubmit: false,
|
|
32
|
-
disabled: false,
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const emit = defineEmits<{
|
|
36
|
-
(e: "submit", values: Record<string, unknown>): void;
|
|
37
|
-
(e: "reset"): void;
|
|
38
|
-
(e: "error", errors: LuFormErrors): void;
|
|
39
|
-
}>();
|
|
40
|
-
|
|
41
|
-
const fields = new Map<string, LuFormFieldRegistration>();
|
|
42
|
-
const errors = ref<LuFormErrors>({});
|
|
43
|
-
const pending = ref(false);
|
|
44
|
-
const hasErrors = computed(() => Object.keys(errors.value).length > 0);
|
|
45
|
-
|
|
46
|
-
async function validate(): Promise<boolean> {
|
|
47
|
-
const nextErrors: LuFormErrors = {};
|
|
48
|
-
|
|
49
|
-
for (const [name, field] of fields) {
|
|
50
|
-
const rule = props.rules?.[name];
|
|
51
|
-
if (!rule) continue;
|
|
52
|
-
|
|
53
|
-
const validators = Array.isArray(rule) ? rule : [rule];
|
|
54
|
-
const value = field.getValue();
|
|
55
|
-
|
|
56
|
-
for (const validator of validators) {
|
|
57
|
-
const result = await validator(value);
|
|
58
|
-
if (result) {
|
|
59
|
-
nextErrors[name] = result;
|
|
60
|
-
field.setError(result);
|
|
61
|
-
break;
|
|
62
|
-
} else {
|
|
63
|
-
field.setError(null);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
errors.value = nextErrors;
|
|
69
|
-
return Object.keys(nextErrors).length === 0;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async function handleSubmit() {
|
|
73
|
-
pending.value = true;
|
|
74
|
-
try {
|
|
75
|
-
const valid = await validate();
|
|
76
|
-
if (!valid) {
|
|
77
|
-
emit("error", errors.value);
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
const values: Record<string, unknown> = {};
|
|
81
|
-
for (const [name, field] of fields) {
|
|
82
|
-
values[name] = field.getValue();
|
|
83
|
-
}
|
|
84
|
-
emit("submit", values);
|
|
85
|
-
if (props.resetOnSubmit) handleReset();
|
|
86
|
-
} finally {
|
|
87
|
-
pending.value = false;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function handleReset() {
|
|
92
|
-
errors.value = {};
|
|
93
|
-
for (const field of fields.values()) {
|
|
94
|
-
field.setValue(undefined);
|
|
95
|
-
field.setError(null);
|
|
96
|
-
}
|
|
97
|
-
emit("reset");
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const context: LuFormContext = {
|
|
101
|
-
register(field) { fields.set(field.name, field); },
|
|
102
|
-
unregister(name) { fields.delete(name); },
|
|
103
|
-
getError(name) { return errors.value[name] ?? null; },
|
|
104
|
-
validateOn: computed(() => props.validateOn),
|
|
105
|
-
disabled: computed(() => props.disabled),
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
provide(LuFormContextKey, context);
|
|
109
|
-
|
|
110
|
-
defineExpose({
|
|
111
|
-
submit: handleSubmit,
|
|
112
|
-
reset: handleReset,
|
|
113
|
-
errors: readonly(errors),
|
|
114
|
-
pending: readonly(pending),
|
|
115
|
-
values: computed(() => {
|
|
116
|
-
const v: Record<string, unknown> = {};
|
|
117
|
-
for (const [name, field] of fields) v[name] = field.getValue();
|
|
118
|
-
return v;
|
|
119
|
-
}),
|
|
120
|
-
});
|
|
121
|
-
</script>
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<span v-bind="$attrs" :class="resolvedSkin" aria-hidden="true" :style="sizeStyle">
|
|
3
|
-
<slot>
|
|
4
|
-
<component
|
|
5
|
-
v-if="resolvedIcon"
|
|
6
|
-
:is="resolvedIcon"
|
|
7
|
-
:size="size"
|
|
8
|
-
:stroke-width="strokeWidth"
|
|
9
|
-
/>
|
|
10
|
-
</slot>
|
|
11
|
-
</span>
|
|
12
|
-
</template>
|
|
13
|
-
|
|
14
|
-
<script setup lang="ts">
|
|
15
|
-
import { computed, useSlots } from "vue";
|
|
16
|
-
import { useLumoraConfig } from "../context";
|
|
17
|
-
|
|
18
|
-
const props = defineProps<{
|
|
19
|
-
variant?: string;
|
|
20
|
-
name?: string;
|
|
21
|
-
size?: number;
|
|
22
|
-
strokeWidth?: number;
|
|
23
|
-
}>();
|
|
24
|
-
|
|
25
|
-
const slots = useSlots();
|
|
26
|
-
const { resolveSkin, resolveIcon } = useLumoraConfig();
|
|
27
|
-
|
|
28
|
-
const resolvedSkin = computed(() => resolveSkin("LuIcon", props.variant));
|
|
29
|
-
|
|
30
|
-
const resolvedIcon = computed(() => {
|
|
31
|
-
if (slots.default) return null;
|
|
32
|
-
if (!props.name) return null;
|
|
33
|
-
return resolveIcon(props.name, props.size);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
const sizeStyle = computed(() =>
|
|
37
|
-
props.size ? { width: `${props.size}px`, height: `${props.size}px` } : undefined
|
|
38
|
-
);
|
|
39
|
-
</script>
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="relative w-full" v-if="$slots.prepend || $slots.append">
|
|
3
|
-
<div v-if="$slots.prepend" :class="prependSkin">
|
|
4
|
-
<slot name="prepend" />
|
|
5
|
-
</div>
|
|
6
|
-
<input
|
|
7
|
-
v-bind="$attrs"
|
|
8
|
-
:class="[resolvedSkin, $slots.prepend && 'pl-9', $slots.append && 'pr-9']"
|
|
9
|
-
:value="modelValue"
|
|
10
|
-
:name="name"
|
|
11
|
-
:disabled="formContext?.disabled.value"
|
|
12
|
-
@input="onInput"
|
|
13
|
-
@blur="onBlur"
|
|
14
|
-
/>
|
|
15
|
-
<div v-if="$slots.append" :class="appendSkin">
|
|
16
|
-
<slot name="append" />
|
|
17
|
-
</div>
|
|
18
|
-
</div>
|
|
19
|
-
<input
|
|
20
|
-
v-else
|
|
21
|
-
v-bind="$attrs"
|
|
22
|
-
:class="resolvedSkin"
|
|
23
|
-
:value="modelValue"
|
|
24
|
-
:name="name"
|
|
25
|
-
:disabled="formContext?.disabled.value"
|
|
26
|
-
@input="onInput"
|
|
27
|
-
@blur="onBlur"
|
|
28
|
-
/>
|
|
29
|
-
</template>
|
|
30
|
-
|
|
31
|
-
<script setup lang="ts">
|
|
32
|
-
import { computed, inject, onMounted, onUnmounted, ref } from "vue";
|
|
33
|
-
import { useLumoraConfig } from "../context";
|
|
34
|
-
import { LuFormContextKey } from "./LuForm.types";
|
|
35
|
-
|
|
36
|
-
const props = defineProps<{
|
|
37
|
-
modelValue?: string | number;
|
|
38
|
-
variant?: string;
|
|
39
|
-
name?: string;
|
|
40
|
-
error?: string | null;
|
|
41
|
-
}>();
|
|
42
|
-
|
|
43
|
-
const emit = defineEmits<{
|
|
44
|
-
(e: "update:modelValue", value: string): void;
|
|
45
|
-
(e: "blur"): void;
|
|
46
|
-
}>();
|
|
47
|
-
|
|
48
|
-
const { resolveSkin } = useLumoraConfig();
|
|
49
|
-
const resolvedSkin = computed(() => resolveSkin("LuInput", props.variant));
|
|
50
|
-
const prependSkin = computed(() => resolveSkin("LuInputPrepend", props.variant));
|
|
51
|
-
const appendSkin = computed(() => resolveSkin("LuInputAppend", props.variant));
|
|
52
|
-
|
|
53
|
-
const formContext = inject(LuFormContextKey, null);
|
|
54
|
-
const internalValue = ref<string | number | undefined>(props.modelValue);
|
|
55
|
-
|
|
56
|
-
const onInput = (event: Event) => {
|
|
57
|
-
const value = (event.target as HTMLInputElement).value;
|
|
58
|
-
internalValue.value = value;
|
|
59
|
-
emit("update:modelValue", value);
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const onBlur = () => {
|
|
63
|
-
if (props.name && formContext && (formContext.validateOn.value === "blur" || formContext.validateOn.value === "both")) {
|
|
64
|
-
// trigger single-field validation — handled by parent LuForm
|
|
65
|
-
}
|
|
66
|
-
emit("blur");
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
onMounted(() => {
|
|
70
|
-
if (!props.name || !formContext) return;
|
|
71
|
-
formContext.register({
|
|
72
|
-
name: props.name,
|
|
73
|
-
getValue: () => internalValue.value,
|
|
74
|
-
setValue: (v) => { internalValue.value = v as string; },
|
|
75
|
-
setError: (_msg) => { /* error display handled via formContext.getError in template if desired */ },
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
onUnmounted(() => {
|
|
80
|
-
if (props.name && formContext) formContext.unregister(props.name);
|
|
81
|
-
});
|
|
82
|
-
</script>
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<component
|
|
3
|
-
:is="componentType"
|
|
4
|
-
v-bind="bindingProps"
|
|
5
|
-
:class="resolvedSkin"
|
|
6
|
-
@click="emit('click', $event)"
|
|
7
|
-
>
|
|
8
|
-
<slot />
|
|
9
|
-
</component>
|
|
10
|
-
</template>
|
|
11
|
-
|
|
12
|
-
<script setup lang="ts">
|
|
13
|
-
import { computed, resolveComponent } from "vue";
|
|
14
|
-
import { useLumoraConfig } from "../context";
|
|
15
|
-
|
|
16
|
-
const props = defineProps<{
|
|
17
|
-
variant?: string;
|
|
18
|
-
as?: string;
|
|
19
|
-
to?: any;
|
|
20
|
-
href?: string;
|
|
21
|
-
target?: string;
|
|
22
|
-
}>();
|
|
23
|
-
|
|
24
|
-
const emit = defineEmits<{
|
|
25
|
-
(e: "click", event: MouseEvent): void;
|
|
26
|
-
}>();
|
|
27
|
-
|
|
28
|
-
const componentType = computed(() => {
|
|
29
|
-
if (props.as) return props.as;
|
|
30
|
-
if (props.to) {
|
|
31
|
-
// Attempt to resolve RouterLink if available, otherwise fallback to 'a'
|
|
32
|
-
const routerLink = resolveComponent("RouterLink");
|
|
33
|
-
return typeof routerLink === 'string' ? 'a' : routerLink;
|
|
34
|
-
}
|
|
35
|
-
return "a";
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const bindingProps = computed(() => {
|
|
39
|
-
const p: any = { ...props };
|
|
40
|
-
delete p.variant;
|
|
41
|
-
delete p.as;
|
|
42
|
-
return p;
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const { resolveSkin } = useLumoraConfig();
|
|
46
|
-
const resolvedSkin = computed(() => resolveSkin("LuLink", props.variant));
|
|
47
|
-
</script>
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div :class="resolvedSkin" ref="dropdownRef">
|
|
3
|
-
<div @click="toggle" :class="resolvedTriggerSkin" aria-haspopup="true" :aria-expanded="isOpen">
|
|
4
|
-
<slot name="trigger">
|
|
5
|
-
<LuButton variant="default">Options <LuIcon name="chevron-down" class="ml-2 h-4 w-4" /></LuButton>
|
|
6
|
-
</slot>
|
|
7
|
-
</div>
|
|
8
|
-
<transition
|
|
9
|
-
enter-active-class="transition ease-out duration-100"
|
|
10
|
-
enter-from-class="transform opacity-0 scale-95"
|
|
11
|
-
enter-to-class="transform opacity-100 scale-100"
|
|
12
|
-
leave-active-class="transition ease-in duration-75"
|
|
13
|
-
leave-from-class="transform opacity-100 scale-100"
|
|
14
|
-
leave-to-class="transform opacity-0 scale-95"
|
|
15
|
-
>
|
|
16
|
-
<div v-if="isOpen" :class="[resolvedContentSkin, alignClass]">
|
|
17
|
-
<div :class="resolvedGroupSkin" role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
|
|
18
|
-
<slot />
|
|
19
|
-
</div>
|
|
20
|
-
</div>
|
|
21
|
-
</transition>
|
|
22
|
-
</div>
|
|
23
|
-
</template>
|
|
24
|
-
|
|
25
|
-
<script setup lang="ts">
|
|
26
|
-
import { computed, ref, onMounted, onBeforeUnmount } from "vue";
|
|
27
|
-
import { useLumoraConfig } from "../context";
|
|
28
|
-
import LuButton from "./LuButton.vue";
|
|
29
|
-
import LuIcon from "./LuIcon.vue";
|
|
30
|
-
|
|
31
|
-
const props = withDefaults(defineProps<{
|
|
32
|
-
variant?: string;
|
|
33
|
-
align?: 'left' | 'right';
|
|
34
|
-
}>(), {
|
|
35
|
-
align: 'left'
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const emit = defineEmits<{
|
|
39
|
-
(e: "open"): void;
|
|
40
|
-
(e: "close"): void;
|
|
41
|
-
}>();
|
|
42
|
-
|
|
43
|
-
const isOpen = ref(false);
|
|
44
|
-
const dropdownRef = ref<HTMLElement | null>(null);
|
|
45
|
-
|
|
46
|
-
const { resolveSkin } = useLumoraConfig();
|
|
47
|
-
|
|
48
|
-
const resolvedSkin = computed(() => resolveSkin("LuMenu", props.variant));
|
|
49
|
-
const resolvedTriggerSkin = computed(() => resolveSkin("LuMenuTrigger", props.variant));
|
|
50
|
-
const resolvedContentSkin = computed(() => resolveSkin("LuMenuContent", props.variant));
|
|
51
|
-
const resolvedGroupSkin = computed(() => resolveSkin("LuMenuGroup", props.variant));
|
|
52
|
-
|
|
53
|
-
const alignClass = computed(() => {
|
|
54
|
-
return props.align === 'right' ? 'right-0 origin-top-right' : 'left-0 origin-top-left';
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
const toggle = () => {
|
|
58
|
-
isOpen.value = !isOpen.value;
|
|
59
|
-
if (isOpen.value) {
|
|
60
|
-
emit("open");
|
|
61
|
-
} else {
|
|
62
|
-
emit("close");
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const close = () => {
|
|
67
|
-
if (isOpen.value) {
|
|
68
|
-
isOpen.value = false;
|
|
69
|
-
emit("close");
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const handleClickOutside = (event: MouseEvent) => {
|
|
74
|
-
if (dropdownRef.value && !dropdownRef.value.contains(event.target as Node)) {
|
|
75
|
-
close();
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
onMounted(() => {
|
|
80
|
-
document.addEventListener('click', handleClickOutside);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
onBeforeUnmount(() => {
|
|
84
|
-
document.removeEventListener('click', handleClickOutside);
|
|
85
|
-
});
|
|
86
|
-
</script>
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<button
|
|
3
|
-
type="button"
|
|
4
|
-
:class="resolvedSkin"
|
|
5
|
-
role="menuitem"
|
|
6
|
-
:disabled="disabled"
|
|
7
|
-
:data-disabled="disabled ? '' : undefined"
|
|
8
|
-
@click="onClick"
|
|
9
|
-
>
|
|
10
|
-
<slot />
|
|
11
|
-
</button>
|
|
12
|
-
</template>
|
|
13
|
-
|
|
14
|
-
<script setup lang="ts">
|
|
15
|
-
import { computed } from "vue";
|
|
16
|
-
import { useLumoraConfig } from "../context";
|
|
17
|
-
|
|
18
|
-
const props = defineProps<{
|
|
19
|
-
variant?: string;
|
|
20
|
-
disabled?: boolean;
|
|
21
|
-
}>();
|
|
22
|
-
|
|
23
|
-
const emit = defineEmits<{
|
|
24
|
-
(e: "click", event: MouseEvent): void;
|
|
25
|
-
}>();
|
|
26
|
-
|
|
27
|
-
const { resolveSkin } = useLumoraConfig();
|
|
28
|
-
const resolvedSkin = computed(() => resolveSkin("LuMenuItem", props.variant));
|
|
29
|
-
|
|
30
|
-
const onClick = (event: MouseEvent) => {
|
|
31
|
-
if (props.disabled) {
|
|
32
|
-
event.preventDefault();
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
emit("click", event);
|
|
36
|
-
};
|
|
37
|
-
</script>
|