pgo-uiux2 1.0.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.
- package/.env +1 -0
- package/.env.production +1 -0
- package/.prettierrc +13 -0
- package/.vscode/extensions.json +3 -0
- package/BUTTON_GUIDE.md +257 -0
- package/README.md +49 -0
- package/THEME_REFERENCE.md +310 -0
- package/eslint.config.ts +27 -0
- package/index.html +13 -0
- package/package.json +85 -0
- package/public/favicon.ico +0 -0
- package/src/App.vue +368 -0
- package/src/assets/fonts/Faruma.ttf +0 -0
- package/src/components/examples/AppBarExample.vue +101 -0
- package/src/components/examples/AvatarExample.vue +47 -0
- package/src/components/examples/BannerExample.vue +287 -0
- package/src/components/examples/BaseInputExample.vue +25 -0
- package/src/components/examples/BreadcrumbExample.vue +53 -0
- package/src/components/examples/CardExample.vue +77 -0
- package/src/components/examples/ChipExample.vue +225 -0
- package/src/components/examples/DatePickerExample.vue +31 -0
- package/src/components/examples/DropdownExample.vue +84 -0
- package/src/components/examples/EditorExample.vue +200 -0
- package/src/components/examples/ExpansionPanelExample.vue +42 -0
- package/src/components/examples/FileUploadExample.vue +40 -0
- package/src/components/examples/FormExample.vue +121 -0
- package/src/components/examples/HugeTest.vue +8 -0
- package/src/components/examples/LayoutContainerExample.vue +80 -0
- package/src/components/examples/ModalExample.vue +82 -0
- package/src/components/examples/NavDrawerExample.vue +170 -0
- package/src/components/examples/NumberFieldExample.vue +145 -0
- package/src/components/examples/RadioButtonExample.vue +161 -0
- package/src/components/examples/SearchExample.vue +322 -0
- package/src/components/examples/SelectExample.vue +121 -0
- package/src/components/examples/StackedTableViewExample.vue +53 -0
- package/src/components/examples/TabExample.vue +336 -0
- package/src/components/examples/TableExample.vue +228 -0
- package/src/components/examples/TextFieldExample.vue +181 -0
- package/src/components/examples/TextareaExample.vue +173 -0
- package/src/components/examples/ThemeToggle.vue +50 -0
- package/src/components/examples/TimelineExample.vue +66 -0
- package/src/components/examples/TipTapEditorExample.vue +20 -0
- package/src/components/examples/TooltipExample.vue +53 -0
- package/src/components/examples/VueDatePickerShowcase.vue +214 -0
- package/src/components/examples/_DatePickerExample.vue +33 -0
- package/src/components/examples/__FormExample.vue +77 -0
- package/src/components/index.ts +25 -0
- package/src/components/pgo/AppBar.vue +347 -0
- package/src/components/pgo/Avatar.vue +139 -0
- package/src/components/pgo/Banner.vue +300 -0
- package/src/components/pgo/Breadcrumb.vue +101 -0
- package/src/components/pgo/Button.vue +171 -0
- package/src/components/pgo/Card.vue +178 -0
- package/src/components/pgo/ConfirmationModel.vue +32 -0
- package/src/components/pgo/DataTable.vue +845 -0
- package/src/components/pgo/DatePicker/CalendarPanel.vue +43 -0
- package/src/components/pgo/DatePicker/__DatePicker.vue +122 -0
- package/src/components/pgo/DatePicker/types.ts +11 -0
- package/src/components/pgo/DatePicker/useCalendar.ts +39 -0
- package/src/components/pgo/DatePicker/useDatePicker.ts +31 -0
- package/src/components/pgo/Deprecated/ToastContainer.vue +51 -0
- package/src/components/pgo/Deprecated/ToastItem.vue +55 -0
- package/src/components/pgo/Dropdown.vue +296 -0
- package/src/components/pgo/DropdownItem.vue +40 -0
- package/src/components/pgo/Editor.vue +511 -0
- package/src/components/pgo/ExpansionPanel.vue +185 -0
- package/src/components/pgo/Footer.vue +39 -0
- package/src/components/pgo/HeroIcon.vue +124 -0
- package/src/components/pgo/InputSearch.vue +194 -0
- package/src/components/pgo/LayoutContainer.vue +104 -0
- package/src/components/pgo/Main.vue +37 -0
- package/src/components/pgo/Modal.vue +273 -0
- package/src/components/pgo/NavDrawer.vue +127 -0
- package/src/components/pgo/NavDrawerItem.vue +161 -0
- package/src/components/pgo/NavigationDrawer.vue +849 -0
- package/src/components/pgo/OLDNavDrawer.vue +661 -0
- package/src/components/pgo/OldAppBar.vue +223 -0
- package/src/components/pgo/PApp.vue +102 -0
- package/src/components/pgo/Pagination.vue +242 -0
- package/src/components/pgo/Search copy.vue +310 -0
- package/src/components/pgo/Search.vue +411 -0
- package/src/components/pgo/StackedTableView.vue +167 -0
- package/src/components/pgo/Tab.vue +617 -0
- package/src/components/pgo/TestInput.vue +395 -0
- package/src/components/pgo/Timeline.vue +367 -0
- package/src/components/pgo/TimelineItem.vue +80 -0
- package/src/components/pgo/TipTapEditor.vue +315 -0
- package/src/components/pgo/Tooltip.NOTES.md +12 -0
- package/src/components/pgo/Tooltip.PROPS.md +21 -0
- package/src/components/pgo/Tooltip.vue +281 -0
- package/src/components/pgo/base/Base.vue +444 -0
- package/src/components/pgo/buttons/Chip.vue +324 -0
- package/src/components/pgo/buttons/ChipGroup.vue +224 -0
- package/src/components/pgo/buttons/Radio.vue +424 -0
- package/src/components/pgo/filters/FilterSection.vue +188 -0
- package/src/components/pgo/filters/Searchbar.vue +216 -0
- package/src/components/pgo/forms/DynamicForm.vue +45 -0
- package/src/components/pgo/forms/Form.vue +132 -0
- package/src/components/pgo/index.ts +15 -0
- package/src/components/pgo/inputs/Checkbox.vue +320 -0
- package/src/components/pgo/inputs/DatePicker.vue +395 -0
- package/src/components/pgo/inputs/FileUpload.vue +326 -0
- package/src/components/pgo/inputs/NumberField.vue +243 -0
- package/src/components/pgo/inputs/Radio.vue +162 -0
- package/src/components/pgo/inputs/RadioGroup.vue +188 -0
- package/src/components/pgo/inputs/Select.vue +535 -0
- package/src/components/pgo/inputs/TextField.vue +194 -0
- package/src/components/pgo/inputs/Textarea.vue +181 -0
- package/src/main.js +12 -0
- package/src/pgo-components/_index.js +31 -0
- package/src/pgo-components/assets/fonts/Faruma.ttf +0 -0
- package/src/pgo-components/assets/fonts/logo.png +0 -0
- package/src/pgo-components/composables/useTheme.js +10 -0
- package/src/pgo-components/directives/tooltip-directive.ts +393 -0
- package/src/pgo-components/index.js +96 -0
- package/src/pgo-components/lib/componentConfig.js +147 -0
- package/src/pgo-components/lib/core/composables/_useCalendar.ts +127 -0
- package/src/pgo-components/lib/core/composables/useDefaults.ts +15 -0
- package/src/pgo-components/lib/core/composables/useLanguageSelect.js +0 -0
- package/src/pgo-components/lib/core/composables/useRtl.ts +12 -0
- package/src/pgo-components/lib/core/defaults/createDefaults.ts +5 -0
- package/src/pgo-components/lib/core/defaults/defaults.ts +7 -0
- package/src/pgo-components/lib/core/rtl/rtl.ts +3 -0
- package/src/pgo-components/lib/core/rtl/setRtl.ts +19 -0
- package/src/pgo-components/lib/drawerState.ts +3 -0
- package/src/pgo-components/lib/i18n/defaultLables.js +71 -0
- package/src/pgo-components/lib/i18n/i18nPlugin.js +52 -0
- package/src/pgo-components/lib/i18n/useI18n.js +35 -0
- package/src/pgo-components/lib/index.ts +38 -0
- package/src/pgo-components/pages/Component.vue +7 -0
- package/src/pgo-components/pages/ComponentRenderer.vue +85 -0
- package/src/pgo-components/pages/Home.vue +130 -0
- package/src/pgo-components/pages/ListView.vue +370 -0
- package/src/pgo-components/pages/Page1.vue +296 -0
- package/src/pgo-components/pages/_Page1.vue +180 -0
- package/src/pgo-components/plugins/SnackBar.vue +251 -0
- package/src/pgo-components/plugins/SnackBarContainer.vue +53 -0
- package/src/pgo-components/plugins/SnackBarPlugin.ts +136 -0
- package/src/pgo-components/plugins/theme-plugin.js +114 -0
- package/src/pgo-components/plugins/types.ts +46 -0
- package/src/pgo-components/plugins/useSnackBar.js +11 -0
- package/src/pgo-components/plugins/useSnackBar.ts +21 -0
- package/src/pgo-components/plugins/validation-plugin.js +11 -0
- package/src/pgo-components/services/Entry.json +813 -0
- package/src/pgo-components/services/axios.js +54 -0
- package/src/pgo-components/services/data.json +90 -0
- package/src/pgo-components/services/person.json +260 -0
- package/src/pgo-components/services/toast.ts +44 -0
- package/src/pgo-components/styles/global.css +234 -0
- package/src/pgo-components/styles/reset.css +96 -0
- package/src/pgo-components/styles/tokens.css +18 -0
- package/src/pgo-components/styles/utilities/border-radius.css +57 -0
- package/src/pgo-components/styles/utilities/borders.css +85 -0
- package/src/pgo-components/styles/utilities/colors.css +38 -0
- package/src/pgo-components/styles/utilities/cursor.css +19 -0
- package/src/pgo-components/styles/utilities/display.css +78 -0
- package/src/pgo-components/styles/utilities/elevation.css +33 -0
- package/src/pgo-components/styles/utilities/flex.css +403 -0
- package/src/pgo-components/styles/utilities/float.css +41 -0
- package/src/pgo-components/styles/utilities/hover.css +9 -0
- package/src/pgo-components/styles/utilities/index.css +18 -0
- package/src/pgo-components/styles/utilities/opacity.css +27 -0
- package/src/pgo-components/styles/utilities/overflow.css +26 -0
- package/src/pgo-components/styles/utilities/palette.css +515 -0
- package/src/pgo-components/styles/utilities/position.css +14 -0
- package/src/pgo-components/styles/utilities/sizing.css +70 -0
- package/src/pgo-components/styles/utilities/spacing.css +578 -0
- package/src/pgo-components/styles/utilities/transitions.css +58 -0
- package/src/pgo-components/styles/utilities/typography.css +91 -0
- package/src/pgo-components/styles/utilities/z-index.css +11 -0
- package/src/pgo-components/tokens/index.js +337 -0
- package/src/router/index.js +88 -0
- package/src/shims-vue.d.ts +14 -0
- package/src/validations/validationRules.js +50 -0
- package/tailwind.config.js +73 -0
- package/test.php +5 -0
- package/tsconfig.json +25 -0
- package/ui +31 -0
- package/ui.pgo.mv.conf +18 -0
- package/vite.config.js +42 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<footer ref="el" :class="['w-full', densityClass]" :style="style">
|
|
3
|
+
<slot />
|
|
4
|
+
</footer>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { ref, onMounted, onBeforeUnmount, computed, inject } from "vue";
|
|
9
|
+
|
|
10
|
+
const props = defineProps({
|
|
11
|
+
id: {
|
|
12
|
+
type: String,
|
|
13
|
+
default: () => `footer-${Math.random().toString(36).slice(2, 9)}`,
|
|
14
|
+
},
|
|
15
|
+
dense: { type: Boolean, default: false },
|
|
16
|
+
});
|
|
17
|
+
const layout = inject<any>("layout", null);
|
|
18
|
+
const el = ref<HTMLElement | null>(null);
|
|
19
|
+
const height = ref(0);
|
|
20
|
+
|
|
21
|
+
function update() {
|
|
22
|
+
if (!el.value) return;
|
|
23
|
+
height.value = el.value.getBoundingClientRect().height;
|
|
24
|
+
if (layout) layout.register("footer", props.id, { height: height.value });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
onMounted(() => {
|
|
28
|
+
update();
|
|
29
|
+
const ro = new ResizeObserver(update);
|
|
30
|
+
if (el.value) ro.observe(el.value);
|
|
31
|
+
onBeforeUnmount(() => {
|
|
32
|
+
ro.disconnect();
|
|
33
|
+
if (layout) layout.unregister("footer", props.id);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const densityClass = computed(() => (props.dense ? "h-10" : "h-14"));
|
|
38
|
+
const style = computed(() => ({}));
|
|
39
|
+
</script>
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="IconAsyncComponent"
|
|
4
|
+
v-bind="attrs"
|
|
5
|
+
:class="computedClass"
|
|
6
|
+
:style="computedStyle"
|
|
7
|
+
/>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
import type { Component } from "vue";
|
|
12
|
+
import { defineProps, withDefaults, computed, useAttrs } from "vue";
|
|
13
|
+
import { defineAsyncComponent } from "vue";
|
|
14
|
+
|
|
15
|
+
// Props
|
|
16
|
+
interface Props {
|
|
17
|
+
name: string;
|
|
18
|
+
type?: "solid" | "outline" | "mini" | "micro";
|
|
19
|
+
size?: number | string;
|
|
20
|
+
color?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
24
|
+
type: "outline",
|
|
25
|
+
size: 24,
|
|
26
|
+
color: "currentColor",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const attrs = useAttrs();
|
|
30
|
+
|
|
31
|
+
// Convert icon names like "academic-cap" to "AcademicCapIcon"
|
|
32
|
+
const normalizeName = (str: string) => {
|
|
33
|
+
return (
|
|
34
|
+
str
|
|
35
|
+
.replace(/-([a-z])/g, (_, c) => c.toUpperCase()) // kebab → camel
|
|
36
|
+
.replace(/(?:^|_|\s|-)(\w)/g, (_, c) => c.toUpperCase()) // capitalize
|
|
37
|
+
.replace(/Icon$/, "") + "Icon"
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const iconName = computed(() => normalizeName(props.name));
|
|
42
|
+
|
|
43
|
+
// Async loader
|
|
44
|
+
const IconAsyncComponent = computed(() =>
|
|
45
|
+
defineAsyncComponent({
|
|
46
|
+
loader: async () => {
|
|
47
|
+
let pack;
|
|
48
|
+
|
|
49
|
+
if (props.type === "solid") {
|
|
50
|
+
pack = await import("@heroicons/vue/24/solid");
|
|
51
|
+
} else if (props.type === "outline") {
|
|
52
|
+
pack = await import("@heroicons/vue/24/outline");
|
|
53
|
+
} else if (props.type === "mini") {
|
|
54
|
+
pack = await import("@heroicons/vue/20/solid");
|
|
55
|
+
} else if (props.type === "micro") {
|
|
56
|
+
pack = await import("@heroicons/vue/16/solid");
|
|
57
|
+
} else {
|
|
58
|
+
pack = await import("@heroicons/vue/24/outline");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const icon = (pack as any)[iconName.value];
|
|
62
|
+
|
|
63
|
+
if (!icon) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`Icon "${iconName.value}" not found in ${props.type} icons`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return icon as Component;
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
// Loading icon
|
|
73
|
+
loadingComponent: defineAsyncComponent(
|
|
74
|
+
async () => (await import("@heroicons/vue/24/outline")).ArrowPathIcon
|
|
75
|
+
),
|
|
76
|
+
|
|
77
|
+
// Error fallback icon
|
|
78
|
+
errorComponent: defineAsyncComponent(
|
|
79
|
+
async () =>
|
|
80
|
+
(await import("@heroicons/vue/24/outline")).ExclamationTriangleIcon
|
|
81
|
+
),
|
|
82
|
+
|
|
83
|
+
delay: 200,
|
|
84
|
+
timeout: 5000,
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// Tailwind color detection
|
|
89
|
+
const isTailwindColorClass = (value?: string) =>
|
|
90
|
+
value
|
|
91
|
+
? /(?:^|\s)(?:text-|fill-|stroke-|bg-|ring-|border-)/.test(value)
|
|
92
|
+
: false;
|
|
93
|
+
|
|
94
|
+
const tailwindColorClass = computed(() =>
|
|
95
|
+
isTailwindColorClass(props.color) ? String(props.color) : ""
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const computedClass = computed(() => {
|
|
99
|
+
const classes = ["hero-icon"];
|
|
100
|
+
|
|
101
|
+
if (attrs.class) classes.push(String(attrs.class));
|
|
102
|
+
if (tailwindColorClass.value) classes.push(tailwindColorClass.value);
|
|
103
|
+
|
|
104
|
+
return classes.filter(Boolean).join(" ");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const computedStyle = computed(() => {
|
|
108
|
+
const styleObj: Record<string, any> =
|
|
109
|
+
typeof attrs.style === "object" ? { ...attrs.style } : {};
|
|
110
|
+
|
|
111
|
+
const sizeCss =
|
|
112
|
+
typeof props.size === "number" ? `${props.size}px` : props.size;
|
|
113
|
+
|
|
114
|
+
styleObj.width = sizeCss;
|
|
115
|
+
styleObj.height = sizeCss;
|
|
116
|
+
|
|
117
|
+
// Inline CSS color if not a Tailwind class
|
|
118
|
+
if (!isTailwindColorClass(props.color)) {
|
|
119
|
+
styleObj.color = props.color;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return styleObj;
|
|
123
|
+
});
|
|
124
|
+
</script>
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
ref="containerRef"
|
|
4
|
+
:class="['relative', containerClass, width]"
|
|
5
|
+
>
|
|
6
|
+
<Base
|
|
7
|
+
:model-value="modelValue"
|
|
8
|
+
:label="label"
|
|
9
|
+
:hint="hint"
|
|
10
|
+
:persistent-hint="!!hint"
|
|
11
|
+
:disabled="disabled"
|
|
12
|
+
:readonly="readonly"
|
|
13
|
+
:required="required"
|
|
14
|
+
:error="!!error || errorMessages.length > 0"
|
|
15
|
+
:error-messages="errorMessages"
|
|
16
|
+
:clearable="clearable && !loading"
|
|
17
|
+
:size="size"
|
|
18
|
+
:id="inputId"
|
|
19
|
+
:prepend="prepend"
|
|
20
|
+
:append="append"
|
|
21
|
+
:is-open="isFocused"
|
|
22
|
+
@click:clear="clear"
|
|
23
|
+
:bg="bg"
|
|
24
|
+
:border="border"
|
|
25
|
+
:text-color="textColor"
|
|
26
|
+
:rounded="rounded"
|
|
27
|
+
:dir="computedDir"
|
|
28
|
+
:lang="computedLang"
|
|
29
|
+
:width="width"
|
|
30
|
+
:rules="rules"
|
|
31
|
+
>
|
|
32
|
+
<template #control="{ attrs, events }">
|
|
33
|
+
<input
|
|
34
|
+
ref="inputRef"
|
|
35
|
+
v-bind="attrs"
|
|
36
|
+
v-on="events"
|
|
37
|
+
type="search"
|
|
38
|
+
:value="modelValue"
|
|
39
|
+
:placeholder="(isFocused && !modelValue) || (!label && !modelValue) ? placeholder : ''"
|
|
40
|
+
:class="inputClasses"
|
|
41
|
+
@input="handleInput"
|
|
42
|
+
@keydown.enter="handleEnter"
|
|
43
|
+
@focus="handleFocus"
|
|
44
|
+
@blur="handleBlur"
|
|
45
|
+
/>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<!-- Loading spinner in append slot -->
|
|
49
|
+
<template v-if="loading" #append>
|
|
50
|
+
<svg class="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
|
|
51
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
52
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 714 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
53
|
+
</svg>
|
|
54
|
+
</template>
|
|
55
|
+
</Base>
|
|
56
|
+
</div>
|
|
57
|
+
</template>
|
|
58
|
+
|
|
59
|
+
<script setup>
|
|
60
|
+
import { ref, computed, inject, onBeforeUnmount } from 'vue'
|
|
61
|
+
import Base from '../pgo/base/Base.vue'
|
|
62
|
+
import { roundedMap, sizes, iconSizes } from '../../pgo-components/lib/componentConfig'
|
|
63
|
+
|
|
64
|
+
const props = defineProps({
|
|
65
|
+
modelValue: { type: [String, Number], default: '' },
|
|
66
|
+
label: { type: [String, Object], default: '' },
|
|
67
|
+
placeholder: { type: String, default: 'Search...' },
|
|
68
|
+
hint: { type: String, default: '' },
|
|
69
|
+
error: { type: String, default: '' },
|
|
70
|
+
errorMessages: { type: Array, default: () => [] },
|
|
71
|
+
disabled: { type: Boolean, default: false },
|
|
72
|
+
readonly: { type: Boolean, default: false },
|
|
73
|
+
required: { type: Boolean, default: false },
|
|
74
|
+
clearable: { type: Boolean, default: true },
|
|
75
|
+
loading: { type: Boolean, default: false },
|
|
76
|
+
prepend: { type: String, default: '' },
|
|
77
|
+
append: { type: String, default: '' },
|
|
78
|
+
rules: { type: Array, default: () => [] }, // ADD THIS
|
|
79
|
+
|
|
80
|
+
// Search behavior
|
|
81
|
+
debounce: { type: Number, default: 300 },
|
|
82
|
+
|
|
83
|
+
// Appearance
|
|
84
|
+
size: { type: String },
|
|
85
|
+
rounded: { type: String },
|
|
86
|
+
border: { type: String},
|
|
87
|
+
textColor: { type: String },
|
|
88
|
+
bg: { type: String },
|
|
89
|
+
containerClass: { type: String, default: '' },
|
|
90
|
+
id: { type: String, default: '' },
|
|
91
|
+
width: { type: String, default: 'w-full' },
|
|
92
|
+
|
|
93
|
+
// RTL/Lang support
|
|
94
|
+
dir: { type: String, default: '' },
|
|
95
|
+
lang: { type: String, default: '' },
|
|
96
|
+
|
|
97
|
+
// Props that might be passed but not used (to avoid warnings)
|
|
98
|
+
items: { type: Array, default: () => [] },
|
|
99
|
+
itemText: { type: String, default: 'text' },
|
|
100
|
+
itemValue: { type: String, default: 'value' },
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
// Inject dir from parent Card (if exists)
|
|
104
|
+
const cardDir = inject('parentDir', '')
|
|
105
|
+
const cardLang = inject('parentLang', '')
|
|
106
|
+
|
|
107
|
+
// Use component's dir if provided, otherwise use card's dir
|
|
108
|
+
const computedDir = computed(() => props.dir || cardDir)
|
|
109
|
+
const computedLang = computed(() => props.lang || cardLang)
|
|
110
|
+
|
|
111
|
+
const emit = defineEmits([
|
|
112
|
+
'update:modelValue',
|
|
113
|
+
'input',
|
|
114
|
+
'change',
|
|
115
|
+
'focus',
|
|
116
|
+
'blur',
|
|
117
|
+
'clear',
|
|
118
|
+
'search',
|
|
119
|
+
'enter'
|
|
120
|
+
])
|
|
121
|
+
|
|
122
|
+
const inputRef = ref(null)
|
|
123
|
+
const containerRef = ref(null)
|
|
124
|
+
const isFocused = ref(false)
|
|
125
|
+
let debounceTimeout = null
|
|
126
|
+
|
|
127
|
+
// Generate unique ID
|
|
128
|
+
const inputId = computed(() => props.id || `search-${Math.random().toString(36).substr(2, 9)}`)
|
|
129
|
+
|
|
130
|
+
// Input classes
|
|
131
|
+
const inputClasses = computed(() => [
|
|
132
|
+
'w-full bg-transparent outline-none border-none',
|
|
133
|
+
'placeholder:text-gray-400',
|
|
134
|
+
'focus:outline-none'
|
|
135
|
+
])
|
|
136
|
+
|
|
137
|
+
// Handlers
|
|
138
|
+
const handleInput = (e) => {
|
|
139
|
+
const value = e.target.value
|
|
140
|
+
|
|
141
|
+
// Update immediately for label animation
|
|
142
|
+
emit('update:modelValue', value)
|
|
143
|
+
|
|
144
|
+
clearTimeout(debounceTimeout)
|
|
145
|
+
debounceTimeout = setTimeout(() => {
|
|
146
|
+
emit('input', value)
|
|
147
|
+
emit('search', value)
|
|
148
|
+
}, props.debounce)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const handleFocus = (event) => {
|
|
152
|
+
isFocused.value = true
|
|
153
|
+
emit('focus', event)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const handleBlur = (event) => {
|
|
157
|
+
isFocused.value = false
|
|
158
|
+
emit('blur', event)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const handleEnter = (event) => {
|
|
162
|
+
emit('enter', props.modelValue)
|
|
163
|
+
emit('search', props.modelValue)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const clear = () => {
|
|
167
|
+
emit('update:modelValue', '')
|
|
168
|
+
emit('clear')
|
|
169
|
+
inputRef.value?.focus()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Focus method
|
|
173
|
+
const focus = () => {
|
|
174
|
+
inputRef.value?.focus()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Lifecycle
|
|
178
|
+
onBeforeUnmount(() => {
|
|
179
|
+
clearTimeout(debounceTimeout)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
// Expose methods
|
|
183
|
+
defineExpose({ focus, clear })
|
|
184
|
+
</script>
|
|
185
|
+
|
|
186
|
+
<style scoped>
|
|
187
|
+
/* Remove default search input styling */
|
|
188
|
+
input[type="search"]::-webkit-search-decoration,
|
|
189
|
+
input[type="search"]::-webkit-search-cancel-button,
|
|
190
|
+
input[type="search"]::-webkit-search-results-button,
|
|
191
|
+
input[type="search"]::-webkit-search-results-decoration {
|
|
192
|
+
display: none;
|
|
193
|
+
}
|
|
194
|
+
</style>
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex h-screen overflow-hidden">
|
|
3
|
+
<!-- Drawer -->
|
|
4
|
+
<NavDrawer
|
|
5
|
+
|
|
6
|
+
:collapsed="collapsed"
|
|
7
|
+
:class="[
|
|
8
|
+
'flex-shrink-0 bg-white shadow-lg z-30',
|
|
9
|
+
isMobile ? 'fixed inset-y-0 left-0 transform transition-transform duration-300 ease-in-out' : '',
|
|
10
|
+
drawerWidthClass,
|
|
11
|
+
drawerOpenClass
|
|
12
|
+
]"
|
|
13
|
+
@close="closeDrawer"
|
|
14
|
+
@toggleCollapse="toggleCollapse"
|
|
15
|
+
/>
|
|
16
|
+
|
|
17
|
+
<!-- Backdrop for mobile drawer -->
|
|
18
|
+
|
|
19
|
+
<div
|
|
20
|
+
v-if="isMobile && showDrawer"
|
|
21
|
+
class="fixed inset-0 bg-black bg-opacity-40 z-20"
|
|
22
|
+
@click="closeDrawer"
|
|
23
|
+
></div>
|
|
24
|
+
|
|
25
|
+
<!-- Main content area -->
|
|
26
|
+
<div class="flex-1 flex flex-col overflow-hidden">
|
|
27
|
+
<!-- AppBar -->
|
|
28
|
+
<AppBar
|
|
29
|
+
title="This is APP"
|
|
30
|
+
:class="['flex-shrink-0']"
|
|
31
|
+
@toggleDrawer="toggleDrawer"
|
|
32
|
+
/>
|
|
33
|
+
|
|
34
|
+
<!-- Content -->
|
|
35
|
+
<main class="flex-1 overflow-auto bg-gray-50 vts-p-4">
|
|
36
|
+
<slot>
|
|
37
|
+
sadf
|
|
38
|
+
</slot>
|
|
39
|
+
</main>
|
|
40
|
+
|
|
41
|
+
<!-- Optional Footer -->
|
|
42
|
+
<footer v-if="$slots.footer" class="flex-shrink-0 vts-p-4 bg-white shadow-inner">
|
|
43
|
+
<slot name="footer" />
|
|
44
|
+
</footer>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</template>
|
|
48
|
+
|
|
49
|
+
<script setup lang="ts">
|
|
50
|
+
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
|
51
|
+
import AppBar from '../pgo/AppBar.vue';
|
|
52
|
+
import NavDrawer from '../pgo/NavDrawer.vue';
|
|
53
|
+
|
|
54
|
+
const collapsed = ref(false);
|
|
55
|
+
const showDrawer = ref(false);
|
|
56
|
+
const windowWidth = ref(window.innerWidth);
|
|
57
|
+
|
|
58
|
+
const MOBILE_BREAKPOINT = 768; // Tailwind md
|
|
59
|
+
|
|
60
|
+
// Detect mobile screen
|
|
61
|
+
const isMobile = computed(() => windowWidth.value < MOBILE_BREAKPOINT);
|
|
62
|
+
|
|
63
|
+
// Drawer width
|
|
64
|
+
const drawerWidthClass = computed(() => (collapsed.value ? 'w-16' : 'w-64'));
|
|
65
|
+
|
|
66
|
+
// Mobile drawer open/close transform
|
|
67
|
+
const drawerOpenClass = computed(() => {
|
|
68
|
+
if (!isMobile.value) return '';
|
|
69
|
+
return showDrawer.value ? 'translate-x-0' : '-translate-x-full';
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Toggle collapse
|
|
73
|
+
function toggleCollapse() {
|
|
74
|
+
collapsed.value = !collapsed.value;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Toggle drawer visibility (for mobile)
|
|
78
|
+
function toggleDrawer() {
|
|
79
|
+
showDrawer.value = !showDrawer.value;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function closeDrawer() {
|
|
83
|
+
showDrawer.value = false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Update window width
|
|
87
|
+
function updateWidth() {
|
|
88
|
+
windowWidth.value = window.innerWidth;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
onMounted(() => {
|
|
92
|
+
window.addEventListener('resize', updateWidth);
|
|
93
|
+
});
|
|
94
|
+
onUnmounted(() => {
|
|
95
|
+
window.removeEventListener('resize', updateWidth);
|
|
96
|
+
});
|
|
97
|
+
</script>
|
|
98
|
+
|
|
99
|
+
<style scoped>
|
|
100
|
+
/* Optional: smooth transition for main content margin on desktop when collapsing drawer */
|
|
101
|
+
.flex-1 {
|
|
102
|
+
transition: margin-left 0.3s ease-in-out;
|
|
103
|
+
}
|
|
104
|
+
</style>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<main
|
|
3
|
+
:class="['flex-1 w-full transition-padding duration-200', contentClass]"
|
|
4
|
+
ref="el"
|
|
5
|
+
:style="style"
|
|
6
|
+
>
|
|
7
|
+
<slot />
|
|
8
|
+
</main>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script setup lang="ts">
|
|
12
|
+
import { ref, computed, inject, watch, onMounted } from "vue";
|
|
13
|
+
|
|
14
|
+
const layout = inject<any>("layout", null);
|
|
15
|
+
const el = ref<HTMLElement | null>(null);
|
|
16
|
+
|
|
17
|
+
const contentClass = computed(() => "");
|
|
18
|
+
|
|
19
|
+
const style = computed(() => {
|
|
20
|
+
// console.log("Layout in Main.vue horizontal:", layout?.horizontalOffset?.value);
|
|
21
|
+
const top = layout?.topOffset?.value || 0;
|
|
22
|
+
const bottom = layout?.bottomOffset?.value || 0;
|
|
23
|
+
const { left, right } = layout?.horizontalOffset?.value || {
|
|
24
|
+
left: 0,
|
|
25
|
+
right: 0,
|
|
26
|
+
};
|
|
27
|
+
return {
|
|
28
|
+
paddingTop: `${top}px`,
|
|
29
|
+
paddingBottom: `${bottom}px`,
|
|
30
|
+
paddingLeft: left ? `${left}px` : undefined,
|
|
31
|
+
paddingRight: right ? `${right}px` : undefined,
|
|
32
|
+
minHeight: `calc(100vh - ${bottom}px)`,
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
onMounted(() => {});
|
|
37
|
+
</script>
|