@myelophone/nuxt 0.18.2
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/.devcontainer/Dockerfile +7 -0
- package/.devcontainer/devcontainer.json +21 -0
- package/.editorconfig +43 -0
- package/.github/workflows/package.yml +73 -0
- package/.nuxtignore +6 -0
- package/.releaserc.js +132 -0
- package/.yarn/releases/yarn-4.18.0.cjs +1000 -0
- package/.yarnrc.yml +8 -0
- package/CHANGELOG.md +141 -0
- package/LICENSE +77 -0
- package/README.md +3004 -0
- package/app/app.vue +455 -0
- package/app/assets/css/animation.css +81 -0
- package/app/assets/css/backgrounds.css +57 -0
- package/app/assets/css/base.css +182 -0
- package/app/assets/css/index.css +9 -0
- package/app/assets/css/links.css +12 -0
- package/app/assets/css/multimedia.css +102 -0
- package/app/assets/css/preloader.css +68 -0
- package/app/assets/css/print.css +40 -0
- package/app/assets/css/tailwind.css +20 -0
- package/app/assets/css/theme.css +29 -0
- package/app/components/consent/GoogleMap.vue +59 -0
- package/app/components/consent/Youtube.vue +27 -0
- package/app/components/cookie/Banner.vue +70 -0
- package/app/components/cookie/ConsentWrapper.vue +71 -0
- package/app/components/cookie/PrivacyPolicy.vue +179 -0
- package/app/components/cookie/SettingsModal.vue +387 -0
- package/app/components/grid/Col.vue +262 -0
- package/app/components/grid/Container.vue +236 -0
- package/app/components/grid/Row.vue +248 -0
- package/app/components/grid/Stack.vue +233 -0
- package/app/components/myelophone/Copyright.vue +5 -0
- package/app/components/myelophone/Welcome.vue +107 -0
- package/app/components/page/FullscreenPreloader.vue +177 -0
- package/app/components/page/Preloader.vue +18 -0
- package/app/components/page/ReadyLocalized.vue +19 -0
- package/app/components/ready/.gitkeep +0 -0
- package/app/components/safe/Email.vue +52 -0
- package/app/components/safe/ImgWithLoader.vue +180 -0
- package/app/components/safe/NuxtImg.vue +25 -0
- package/app/components/safe/NuxtPicture.vue +29 -0
- package/app/components/section/.gitkeep +0 -0
- package/app/components/seo/BrandInfo.vue +76 -0
- package/app/components/seo/ContentNoIndex.vue +35 -0
- package/app/components/seo/NoIndex.vue +22 -0
- package/app/components/seo/OrgInfo.vue +130 -0
- package/app/components/seo/ProfileInfo.vue +137 -0
- package/app/components/ui/Accordion.vue +163 -0
- package/app/components/ui/Alert.vue +179 -0
- package/app/components/ui/Avatar.vue +88 -0
- package/app/components/ui/Badge.vue +150 -0
- package/app/components/ui/Banner.vue +133 -0
- package/app/components/ui/Button.vue +175 -0
- package/app/components/ui/Card.vue +66 -0
- package/app/components/ui/Checkbox.vue +134 -0
- package/app/components/ui/Chip.vue +136 -0
- package/app/components/ui/CommandPalette.vue +434 -0
- package/app/components/ui/Console.vue +583 -0
- package/app/components/ui/CursorCreative.vue +148 -0
- package/app/components/ui/FeatureAccordion.vue +165 -0
- package/app/components/ui/FileUpload.vue +705 -0
- package/app/components/ui/GeoDependent.vue +72 -0
- package/app/components/ui/Heading.vue +51 -0
- package/app/components/ui/Icon.vue +42 -0
- package/app/components/ui/Icon8.vue +39 -0
- package/app/components/ui/Input.vue +88 -0
- package/app/components/ui/LangLink.vue +34 -0
- package/app/components/ui/LangVisible.vue +33 -0
- package/app/components/ui/LanguageSelect.vue +202 -0
- package/app/components/ui/LightBox.vue +155 -0
- package/app/components/ui/LocalTime.vue +170 -0
- package/app/components/ui/Modal.vue +161 -0
- package/app/components/ui/Popup.vue +200 -0
- package/app/components/ui/PostContent.server.vue +23 -0
- package/app/components/ui/ProtectedEmail.vue +52 -0
- package/app/components/ui/RelativeTime.vue +141 -0
- package/app/components/ui/ScheduledContent.vue +150 -0
- package/app/components/ui/ScrollToTop.client.vue +48 -0
- package/app/components/ui/SegmentedControl.vue +148 -0
- package/app/components/ui/SiteSearch.vue +1191 -0
- package/app/components/ui/Skeleton.vue +91 -0
- package/app/components/ui/SmartContrast.vue +74 -0
- package/app/components/ui/SnapContainer.vue +222 -0
- package/app/components/ui/SnapSection.vue +294 -0
- package/app/components/ui/SonnerStyle.vue +7 -0
- package/app/components/ui/SplitSection.vue +99 -0
- package/app/components/ui/Steps.vue +858 -0
- package/app/components/ui/StickyWrapper.vue +46 -0
- package/app/components/ui/Table.vue +251 -0
- package/app/components/ui/Tabs.vue +451 -0
- package/app/components/ui/TextColumn.vue +36 -0
- package/app/components/ui/Textarea.vue +251 -0
- package/app/components/ui/Toggler.vue +40 -0
- package/app/components/ui/Tooltip.vue +524 -0
- package/app/components/ui/TruncateText.vue +105 -0
- package/app/components/ui/ViewportSpacer.vue +72 -0
- package/app/components/view/AnimatedCounter.vue +121 -0
- package/app/components/view/AnnouncementBar.vue +104 -0
- package/app/components/view/AvatarGroup.vue +104 -0
- package/app/components/view/Breadcrumbs.vue +97 -0
- package/app/components/view/ContentWithToc.vue +152 -0
- package/app/components/view/Copyright.vue +68 -0
- package/app/components/view/CountdownTimer.vue +184 -0
- package/app/components/view/CreativeCTA.vue +143 -0
- package/app/components/view/CurrencySelect.vue +254 -0
- package/app/components/view/FeatureBoxGrid.vue +142 -0
- package/app/components/view/HorizontalMenu.vue +97 -0
- package/app/components/view/ImageCompare.vue +141 -0
- package/app/components/view/ImgSlider.vue +333 -0
- package/app/components/view/InfiniteMarquee.vue +147 -0
- package/app/components/view/InfoBar.vue +86 -0
- package/app/components/view/LogoGrid.vue +106 -0
- package/app/components/view/LogoSlider.vue +83 -0
- package/app/components/view/NotFound.vue +81 -0
- package/app/components/view/PartialBackground.vue +313 -0
- package/app/components/view/QuoteBig.vue +111 -0
- package/app/components/view/ResponsiveMenu.vue +219 -0
- package/app/components/view/ReviewsSlider.vue +230 -0
- package/app/components/view/SocialBar.vue +78 -0
- package/app/components/view/StickyHeadingContent.vue +158 -0
- package/app/components/view/TallyForm.vue +295 -0
- package/app/components/view/WarningOutdated.vue +113 -0
- package/app/composables/useApi.ts +88 -0
- package/app/composables/useAppSeo.ts +78 -0
- package/app/composables/useBreadcrumbs.ts +106 -0
- package/app/composables/useChangeUrl.ts +73 -0
- package/app/composables/useCommand.ts +28 -0
- package/app/composables/useCookieControl.ts +111 -0
- package/app/composables/useCookieModal.ts +9 -0
- package/app/composables/useDevice.ts +109 -0
- package/app/composables/useGeoFetch.ts +139 -0
- package/app/composables/useLoadScript.ts +36 -0
- package/app/composables/useModalState.ts +37 -0
- package/app/composables/useMultiLang.ts +278 -0
- package/app/composables/useNetwork.ts +49 -0
- package/app/composables/useNuxtVersion.ts +5 -0
- package/app/composables/usePiniaStore.ts +5 -0
- package/app/composables/usePopupControl.ts +57 -0
- package/app/composables/useQuery.ts +307 -0
- package/app/composables/useReadyPage.ts +182 -0
- package/app/composables/useSafeList.ts +90 -0
- package/app/composables/useScriptLoader.ts +221 -0
- package/app/composables/useSiteSearch.ts +32 -0
- package/app/composables/useSnapScroll.ts +24 -0
- package/app/composables/useStatic.ts +5 -0
- package/app/composables/useStorage.ts +252 -0
- package/app/composables/useStoreBroadcast.ts +146 -0
- package/app/composables/useThemeSync.ts +81 -0
- package/app/constants/layout.ts +109 -0
- package/app/constants/predefinedCookieScripts.ts +1222 -0
- package/app/constants/ui-events.ts +1 -0
- package/app/error.vue +169 -0
- package/app/layouts/default-blog.vue +5 -0
- package/app/layouts/default.vue +3 -0
- package/app/locales/en/commands.json +5 -0
- package/app/locales/en/cookies.json +182 -0
- package/app/locales/en/error.json +4 -0
- package/app/locales/en/interface.json +33 -0
- package/app/locales/en/myelophone.json +8 -0
- package/app/locales/en/outdated.json +5 -0
- package/app/locales/en/times.json +18 -0
- package/app/locales/pl/commands.json +5 -0
- package/app/locales/pl/cookies.json +182 -0
- package/app/locales/pl/error.json +4 -0
- package/app/locales/pl/interface.json +33 -0
- package/app/locales/pl/myelophone.json +8 -0
- package/app/locales/pl/outdated.json +5 -0
- package/app/locales/pl/times.json +18 -0
- package/app/locales/ru/commands.json +5 -0
- package/app/locales/ru/cookies.json +182 -0
- package/app/locales/ru/error.json +4 -0
- package/app/locales/ru/interface.json +33 -0
- package/app/locales/ru/myelophone.json +8 -0
- package/app/locales/ru/outdated.json +5 -0
- package/app/locales/ru/times.json +18 -0
- package/app/middleware/blog-layout.ts +28 -0
- package/app/middleware/i18n.global.ts +17 -0
- package/app/middleware/not-found-status.ts +11 -0
- package/app/modules/default-blog/index.ts +39 -0
- package/app/modules/default-blog/posts.ts +80 -0
- package/app/modules/default-sitemap/index.ts +116 -0
- package/app/modules/default-sitemap/runtime/sitemap.get.ts +42 -0
- package/app/modules/fullscreen-preloader/module.ts +43 -0
- package/app/modules/htaccess/.htaccess +38 -0
- package/app/modules/htaccess/index.ts +23 -0
- package/app/modules/i18n-shaker.mjs +221 -0
- package/app/modules/multi18n/module.ts +71 -0
- package/app/modules/myelophone-config/module.ts +241 -0
- package/app/pages/[...all].vue +9 -0
- package/app/pages/index.vue +3 -0
- package/app/pages/post/[...slug].vue +30 -0
- package/app/plugins/bfcache-refresh.client.ts +9 -0
- package/app/plugins/canonical.ts +30 -0
- package/app/plugins/csp-nonce.server.ts +41 -0
- package/app/plugins/multi18n.client.ts +21 -0
- package/app/plugins/multi18n.server.ts +15 -0
- package/app/plugins/nuxt-version.client.ts +4 -0
- package/app/plugins/restore.client.ts +21 -0
- package/app/plugins/reveal.ts +89 -0
- package/app/plugins/url-helper.client.ts +66 -0
- package/app/server/api/exchange-rates.get.ts +216 -0
- package/app/server/api/healthz.ts +1 -0
- package/app/server/api/logout.post.ts +6 -0
- package/app/server/middleware/normalize-url.ts +14 -0
- package/app/server/middleware/security-headers.ts +147 -0
- package/app/server/middleware/url-helper.ts +43 -0
- package/app/server/plugins/clean-html-spaces.ts +15 -0
- package/app/server/plugins/clean-styles.ts +94 -0
- package/app/server/plugins/compression.ts +122 -0
- package/app/server/plugins/css-preload.ts +26 -0
- package/app/server/plugins/early-hints.ts +31 -0
- package/app/server/plugins/force-noindex.ts +43 -0
- package/app/server/plugins/html-one-line.ts +17 -0
- package/app/server/plugins/inline-style.ts +7 -0
- package/app/server/routes/healthz.get.ts +1 -0
- package/app/server/tsconfig.json +3 -0
- package/app/stores/cart.ts +945 -0
- package/app/stores/commands.ts +43 -0
- package/app/stores/device.ts +156 -0
- package/app/stores/querySync.ts +5 -0
- package/app/stores/settings.ts +145 -0
- package/app/stores/user.ts +607 -0
- package/app/types/cookie.ts +89 -0
- package/app/types/myelophone.d.ts +87 -0
- package/app/types/site-search.ts +110 -0
- package/app/utils/asyncRefresh.ts +4 -0
- package/app/utils/blog.server.ts +142 -0
- package/app/utils/jsonLd.ts +56 -0
- package/app/utils/myelophoneConfig.ts +4 -0
- package/app/utils/sortBy.ts +18 -0
- package/app/utils/transliterate.ts +75 -0
- package/app/utils/ytVideo.ts +37 -0
- package/app/workers/site-search.worker.ts +661 -0
- package/biome.json +94 -0
- package/commitlint.config.js +3 -0
- package/nuxt.config.ts +539 -0
- package/package.json +101 -0
- package/playground/content/.gitkeep +0 -0
- package/playground/locales/.gitkeep +0 -0
- package/playground/myelophone.dev.ts +45 -0
- package/playground/nuxt.config.ts +105 -0
- package/playground/package.json +5 -0
- package/playground/pages/.gitkeep +0 -0
- package/playground/tsconfig.json +3 -0
- package/playwright.config.ts +23 -0
- package/public/browsers/chrome.svg +77 -0
- package/public/browsers/edge.svg +98 -0
- package/public/browsers/firefox.svg +300 -0
- package/public/favicon.ico +0 -0
- package/public/myelophone_eng.png +0 -0
- package/public/myelophone_eng_white.png +0 -0
- package/spa-loading-template.html +72 -0
- package/tests/home.spec.ts +9 -0
- package/tests/utils.ts +23 -0
- package/tsconfig.json +16 -0
- package/vite/plugins/clean-css.ts +190 -0
- package/vite/plugins/postcss-add-viewportunits.ts +33 -0
- package/vite/plugins/postcss-viewport-fallback.ts +49 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const props = withDefaults(
|
|
3
|
+
defineProps<{
|
|
4
|
+
label?: string;
|
|
5
|
+
|
|
6
|
+
color?: "primary" | "gray" | "info" | "success" | "warning" | "error";
|
|
7
|
+
variant?: "solid" | "soft" | "outline";
|
|
8
|
+
size?: "xs" | "sm" | "md";
|
|
9
|
+
|
|
10
|
+
rounded?: boolean;
|
|
11
|
+
pill?: boolean;
|
|
12
|
+
|
|
13
|
+
icon?: string;
|
|
14
|
+
leadingIcon?: string;
|
|
15
|
+
trailingIcon?: string;
|
|
16
|
+
showLeading?: boolean;
|
|
17
|
+
showTrailing?: boolean;
|
|
18
|
+
|
|
19
|
+
avatar?: {
|
|
20
|
+
src?: string;
|
|
21
|
+
icon?: string;
|
|
22
|
+
alt?: string;
|
|
23
|
+
};
|
|
24
|
+
}>(),
|
|
25
|
+
{
|
|
26
|
+
color: "gray",
|
|
27
|
+
variant: "soft",
|
|
28
|
+
size: "sm",
|
|
29
|
+
rounded: true,
|
|
30
|
+
pill: false,
|
|
31
|
+
showLeading: true,
|
|
32
|
+
showTrailing: true,
|
|
33
|
+
},
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const sizes = {
|
|
37
|
+
xs: {
|
|
38
|
+
container: "text-xs px-2 py-0.5 gap-1",
|
|
39
|
+
icon: "h-3 w-3",
|
|
40
|
+
avatar: "h-4 w-4",
|
|
41
|
+
},
|
|
42
|
+
sm: {
|
|
43
|
+
container: "text-xs px-3 py-0.5 gap-1.5",
|
|
44
|
+
icon: "h-3.5 w-3.5",
|
|
45
|
+
avatar: "h-5 w-5",
|
|
46
|
+
},
|
|
47
|
+
md: {
|
|
48
|
+
container: "text-sm px-4 py-1 gap-2",
|
|
49
|
+
icon: "h-4 w-4",
|
|
50
|
+
avatar: "h-6 w-6",
|
|
51
|
+
},
|
|
52
|
+
} as const;
|
|
53
|
+
|
|
54
|
+
const colorVariants: Record<
|
|
55
|
+
NonNullable<typeof props.color>,
|
|
56
|
+
Record<NonNullable<typeof props.variant>, string>
|
|
57
|
+
> = {
|
|
58
|
+
gray: {
|
|
59
|
+
solid: "bg-gray-800 text-white",
|
|
60
|
+
soft: "bg-gray-100 text-gray-800",
|
|
61
|
+
outline: "border border-gray-300 text-gray-800",
|
|
62
|
+
},
|
|
63
|
+
primary: {
|
|
64
|
+
solid: "bg-primary-600 text-white",
|
|
65
|
+
soft: "bg-primary-100 text-primary-800",
|
|
66
|
+
outline: "border border-primary-300 text-primary-700",
|
|
67
|
+
},
|
|
68
|
+
info: {
|
|
69
|
+
solid: "bg-blue-600 text-white",
|
|
70
|
+
soft: "bg-blue-100 text-blue-800",
|
|
71
|
+
outline: "border border-blue-300 text-blue-700",
|
|
72
|
+
},
|
|
73
|
+
success: {
|
|
74
|
+
solid: "bg-emerald-600 text-white",
|
|
75
|
+
soft: "bg-emerald-100 text-emerald-800",
|
|
76
|
+
outline: "border border-emerald-300 text-emerald-700",
|
|
77
|
+
},
|
|
78
|
+
warning: {
|
|
79
|
+
solid: "bg-amber-500 text-white",
|
|
80
|
+
soft: "bg-amber-100 text-amber-800",
|
|
81
|
+
outline: "border border-amber-300 text-amber-700",
|
|
82
|
+
},
|
|
83
|
+
error: {
|
|
84
|
+
solid: "bg-red-600 text-white",
|
|
85
|
+
soft: "bg-red-100 text-red-800",
|
|
86
|
+
outline: "border border-red-300 text-red-700",
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const leadingIconName = computed<string | null>(() => {
|
|
91
|
+
return props.leadingIcon ?? props.icon ?? null;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const showLeadingIcon = computed(
|
|
95
|
+
() => props.showLeading && !!leadingIconName.value && !props.avatar,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const showTrailingIcon = computed(
|
|
99
|
+
() => props.showTrailing && !!props.trailingIcon,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const classes = computed(() => [
|
|
103
|
+
"inline-flex items-center font-medium whitespace-nowrap w-fit",
|
|
104
|
+
sizes[props.size].container,
|
|
105
|
+
colorVariants[props.color][props.variant],
|
|
106
|
+
props.pill ? "rounded-full" : props.rounded ? "rounded-md" : "rounded-none",
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
const trailingIconName = computed<string | null>(() => {
|
|
110
|
+
return props.showTrailing && props.trailingIcon ? props.trailingIcon : null;
|
|
111
|
+
});
|
|
112
|
+
</script>
|
|
113
|
+
|
|
114
|
+
<template>
|
|
115
|
+
<span :class="classes">
|
|
116
|
+
<span
|
|
117
|
+
v-if="avatar"
|
|
118
|
+
class="flex items-center justify-center shrink-0"
|
|
119
|
+
:class="sizes[size].avatar"
|
|
120
|
+
>
|
|
121
|
+
<SafeImgWithLoader
|
|
122
|
+
v-if="avatar.src"
|
|
123
|
+
:src="avatar.src"
|
|
124
|
+
:alt="avatar.alt || ''"
|
|
125
|
+
class="h-full w-full rounded-full object-contain"
|
|
126
|
+
/>
|
|
127
|
+
<UiIcon
|
|
128
|
+
v-else-if="avatar.icon"
|
|
129
|
+
:name="avatar.icon"
|
|
130
|
+
class="h-full w-full"
|
|
131
|
+
/>
|
|
132
|
+
</span>
|
|
133
|
+
|
|
134
|
+
<UiIcon
|
|
135
|
+
v-if="showLeadingIcon && leadingIconName"
|
|
136
|
+
:name="leadingIconName"
|
|
137
|
+
:class="sizes[size].icon"
|
|
138
|
+
/>
|
|
139
|
+
|
|
140
|
+
<span>
|
|
141
|
+
<slot>{{ label }}</slot>
|
|
142
|
+
</span>
|
|
143
|
+
|
|
144
|
+
<UiIcon
|
|
145
|
+
v-if="trailingIconName"
|
|
146
|
+
:name="trailingIconName"
|
|
147
|
+
:class="sizes[size].icon"
|
|
148
|
+
/>
|
|
149
|
+
</span>
|
|
150
|
+
</template>
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const props = withDefaults(
|
|
3
|
+
defineProps<{
|
|
4
|
+
title?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
|
|
7
|
+
icon?: string;
|
|
8
|
+
avatar?: {
|
|
9
|
+
src?: string;
|
|
10
|
+
icon?: string;
|
|
11
|
+
alt?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
color?: "primary" | "gray" | "info" | "success" | "warning" | "error";
|
|
15
|
+
variant?: "solid" | "soft" | "outline";
|
|
16
|
+
|
|
17
|
+
closable?: boolean;
|
|
18
|
+
rounded?: boolean;
|
|
19
|
+
bordered?: boolean;
|
|
20
|
+
}>(),
|
|
21
|
+
{
|
|
22
|
+
color: "gray",
|
|
23
|
+
variant: "soft",
|
|
24
|
+
closable: false,
|
|
25
|
+
rounded: true,
|
|
26
|
+
bordered: false,
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const visible = ref(true);
|
|
31
|
+
|
|
32
|
+
const close = () => {
|
|
33
|
+
visible.value = false;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const colorVariants: Record<
|
|
37
|
+
NonNullable<typeof props.color>,
|
|
38
|
+
Record<NonNullable<typeof props.variant>, string>
|
|
39
|
+
> = {
|
|
40
|
+
gray: {
|
|
41
|
+
solid: "bg-gray-800 text-white border border-gray-800/30",
|
|
42
|
+
soft: "bg-gray-100 text-gray-900 border border-gray-300",
|
|
43
|
+
outline: "bg-transparent text-gray-900 border border-gray-400",
|
|
44
|
+
},
|
|
45
|
+
primary: {
|
|
46
|
+
solid: "bg-primary-600 text-white border border-primary-600/30",
|
|
47
|
+
soft: "bg-primary-100 text-primary-900 border border-primary-300",
|
|
48
|
+
outline: "bg-transparent text-primary-900 border border-primary-400",
|
|
49
|
+
},
|
|
50
|
+
info: {
|
|
51
|
+
solid: "bg-blue-600 text-white border border-blue-600/30",
|
|
52
|
+
soft: "bg-blue-100 text-blue-900 border border-blue-300",
|
|
53
|
+
outline: "bg-transparent text-blue-900 border border-blue-400",
|
|
54
|
+
},
|
|
55
|
+
success: {
|
|
56
|
+
solid: "bg-emerald-600 text-white border border-emerald-600/30",
|
|
57
|
+
soft: "bg-emerald-100 text-emerald-900 border border-emerald-300",
|
|
58
|
+
outline: "bg-transparent text-emerald-900 border border-emerald-400",
|
|
59
|
+
},
|
|
60
|
+
warning: {
|
|
61
|
+
solid: "bg-amber-500 text-white border border-amber-500/30",
|
|
62
|
+
soft: "bg-amber-100 text-amber-900 border border-amber-300",
|
|
63
|
+
outline: "bg-transparent text-amber-900 border border-amber-400",
|
|
64
|
+
},
|
|
65
|
+
error: {
|
|
66
|
+
solid: "bg-red-600 text-white border border-red-600/30",
|
|
67
|
+
soft: "bg-red-100 text-red-900 border border-red-300",
|
|
68
|
+
outline: "bg-transparent text-red-900 border border-red-400",
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const classes = computed(() => [
|
|
73
|
+
"w-full flex items-center gap-4 px-4 py-3",
|
|
74
|
+
colorVariants[props.color][props.variant],
|
|
75
|
+
props.rounded ? "rounded-md" : "rounded-none",
|
|
76
|
+
props.bordered || props.variant === "outline"
|
|
77
|
+
? "border border-current/20"
|
|
78
|
+
: null,
|
|
79
|
+
]);
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
<template>
|
|
83
|
+
<div v-if="visible" role="region" :class="classes">
|
|
84
|
+
<div
|
|
85
|
+
v-if="avatar || icon"
|
|
86
|
+
class="flex items-center justify-center shrink-0"
|
|
87
|
+
>
|
|
88
|
+
<div class="h-10 w-10 flex items-center justify-center">
|
|
89
|
+
<SafeImgWithLoader
|
|
90
|
+
v-if="avatar?.src"
|
|
91
|
+
:src="avatar.src"
|
|
92
|
+
:alt="avatar.alt || ''"
|
|
93
|
+
class="h-full w-full object-contain rounded-full"
|
|
94
|
+
/>
|
|
95
|
+
<UiIcon
|
|
96
|
+
v-else-if="avatar?.icon"
|
|
97
|
+
:name="avatar.icon"
|
|
98
|
+
class="h-full w-full"
|
|
99
|
+
/>
|
|
100
|
+
<UiIcon v-else-if="icon" :name="icon" class="h-6 w-6" />
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div class="flex-1 min-w-0">
|
|
105
|
+
<div
|
|
106
|
+
v-if="$slots.title || title"
|
|
107
|
+
class="font-semibold leading-tight"
|
|
108
|
+
>
|
|
109
|
+
<slot name="title">{{ title }}</slot>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<div
|
|
113
|
+
v-if="$slots.default || description"
|
|
114
|
+
class="text-sm opacity-90 leading-relaxed"
|
|
115
|
+
>
|
|
116
|
+
<slot>{{ description }}</slot>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div v-if="$slots.actions" class="flex items-center gap-2 shrink-0">
|
|
121
|
+
<slot name="actions" />
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<button
|
|
125
|
+
v-if="closable"
|
|
126
|
+
@click="close"
|
|
127
|
+
class="ml-2 opacity-60 hover:opacity-100 transition shrink-0"
|
|
128
|
+
aria-label="Close banner"
|
|
129
|
+
>
|
|
130
|
+
<UiIcon name="heroicons:x-mark" class="h-5 w-5" />
|
|
131
|
+
</button>
|
|
132
|
+
</div>
|
|
133
|
+
</template>
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { NuxtLink } from "#components";
|
|
3
|
+
|
|
4
|
+
const props = withDefaults(
|
|
5
|
+
defineProps<{
|
|
6
|
+
as?: "button" | "a" | "nuxt-link" | any;
|
|
7
|
+
|
|
8
|
+
label?: string;
|
|
9
|
+
|
|
10
|
+
to?: string | object;
|
|
11
|
+
href?: string;
|
|
12
|
+
target?: string;
|
|
13
|
+
|
|
14
|
+
color?:
|
|
15
|
+
| "primary"
|
|
16
|
+
| "gray"
|
|
17
|
+
| "info"
|
|
18
|
+
| "success"
|
|
19
|
+
| "warning"
|
|
20
|
+
| "error"
|
|
21
|
+
| "base";
|
|
22
|
+
variant?: "solid" | "soft" | "outline" | "ghost";
|
|
23
|
+
size?: "xs" | "sm" | "md" | "lg";
|
|
24
|
+
|
|
25
|
+
icon?: string;
|
|
26
|
+
leadingIcon?: string;
|
|
27
|
+
trailingIcon?: string;
|
|
28
|
+
|
|
29
|
+
loading?: boolean;
|
|
30
|
+
disabled?: boolean;
|
|
31
|
+
|
|
32
|
+
block?: boolean;
|
|
33
|
+
rounded?: boolean;
|
|
34
|
+
square?: boolean;
|
|
35
|
+
}>(),
|
|
36
|
+
{
|
|
37
|
+
as: "button",
|
|
38
|
+
color: "base",
|
|
39
|
+
variant: "solid",
|
|
40
|
+
size: "md",
|
|
41
|
+
loading: false,
|
|
42
|
+
disabled: false,
|
|
43
|
+
block: false,
|
|
44
|
+
rounded: true,
|
|
45
|
+
square: false,
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const resolvedAs = computed(() => {
|
|
50
|
+
if (props.as === "nuxt-link") return NuxtLink;
|
|
51
|
+
return props.as;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const isDisabled = computed(() => props.disabled || props.loading);
|
|
55
|
+
|
|
56
|
+
const leadingIconName = computed<string | null>(() => {
|
|
57
|
+
return props.leadingIcon ?? props.icon ?? null;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const trailingIconName = computed<string | null>(() => {
|
|
61
|
+
return props.trailingIcon ?? null;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const sizes = {
|
|
65
|
+
xs: "text-xs px-2 py-1 gap-1",
|
|
66
|
+
sm: "text-sm px-2.5 py-1.5 gap-1.5",
|
|
67
|
+
md: "text-sm px-4 py-2 gap-2",
|
|
68
|
+
lg: "text-base px-5 py-2.5 gap-2.5",
|
|
69
|
+
} as const;
|
|
70
|
+
|
|
71
|
+
const iconSizes = {
|
|
72
|
+
xs: "h-3 w-3",
|
|
73
|
+
sm: "h-4 w-4",
|
|
74
|
+
md: "h-5 w-5",
|
|
75
|
+
lg: "h-6 w-6",
|
|
76
|
+
} as const;
|
|
77
|
+
|
|
78
|
+
const baseVariants = {
|
|
79
|
+
solid: "bg-[var(--ui-bg)] text-[var(--ui-text)] border border-[var(--ui-border,var(--ui-text))]",
|
|
80
|
+
soft: "bg-[color-mix(in srgb, var(--ui-bg) 85%, transparent)] text-[var(--ui-text)] border border-[var(--ui-border,var(--ui-text))]",
|
|
81
|
+
outline:
|
|
82
|
+
"bg-transparent text-[var(--ui-text)] border border-[var(--ui-border,var(--ui-text))]",
|
|
83
|
+
ghost: "bg-transparent text-[var(--ui-text)]",
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const colorVariants: Record<
|
|
87
|
+
NonNullable<typeof props.color>,
|
|
88
|
+
Record<NonNullable<typeof props.variant>, string>
|
|
89
|
+
> = {
|
|
90
|
+
base: baseVariants,
|
|
91
|
+
|
|
92
|
+
gray: {
|
|
93
|
+
solid: "bg-gray-800 text-white border border-gray-800/30",
|
|
94
|
+
soft: "bg-gray-100 text-gray-900 border border-gray-300",
|
|
95
|
+
outline: "bg-transparent text-gray-900 border border-gray-400",
|
|
96
|
+
ghost: "bg-transparent text-gray-900",
|
|
97
|
+
},
|
|
98
|
+
primary: {
|
|
99
|
+
solid: "bg-primary-600 text-white border border-primary-600/30",
|
|
100
|
+
soft: "bg-primary-100 text-primary-900 border border-primary-300",
|
|
101
|
+
outline: "bg-transparent text-primary-900 border border-primary-400",
|
|
102
|
+
ghost: "bg-transparent text-primary-900",
|
|
103
|
+
},
|
|
104
|
+
info: {
|
|
105
|
+
solid: "bg-blue-600 text-white border border-blue-600/30",
|
|
106
|
+
soft: "bg-blue-100 text-blue-900 border border-blue-300",
|
|
107
|
+
outline: "bg-transparent text-blue-900 border border-blue-400",
|
|
108
|
+
ghost: "bg-transparent text-blue-900",
|
|
109
|
+
},
|
|
110
|
+
success: {
|
|
111
|
+
solid: "bg-emerald-600 text-white border border-emerald-600/30",
|
|
112
|
+
soft: "bg-emerald-100 text-emerald-900 border border-emerald-300",
|
|
113
|
+
outline: "bg-transparent text-emerald-900 border border-emerald-400",
|
|
114
|
+
ghost: "bg-transparent text-emerald-900",
|
|
115
|
+
},
|
|
116
|
+
warning: {
|
|
117
|
+
solid: "bg-amber-500 text-white border border-amber-500/30",
|
|
118
|
+
soft: "bg-amber-100 text-amber-900 border border-amber-300",
|
|
119
|
+
outline: "bg-transparent text-amber-900 border border-amber-400",
|
|
120
|
+
ghost: "bg-transparent text-amber-900",
|
|
121
|
+
},
|
|
122
|
+
error: {
|
|
123
|
+
solid: "bg-red-600 text-white border border-red-600/30",
|
|
124
|
+
soft: "bg-red-100 text-red-900 border border-red-300",
|
|
125
|
+
outline: "bg-transparent text-red-900 border border-red-400",
|
|
126
|
+
ghost: "bg-transparent text-red-900",
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const classes = computed(() => [
|
|
131
|
+
"inline-flex items-center justify-center font-medium whitespace-nowrap transition",
|
|
132
|
+
sizes[props.size],
|
|
133
|
+
colorVariants[props.color][props.variant],
|
|
134
|
+
props.block ? "w-full" : "w-fit",
|
|
135
|
+
props.square ? "aspect-square p-0" : null,
|
|
136
|
+
props.rounded ? "rounded-md" : "rounded-none",
|
|
137
|
+
isDisabled.value ? "opacity-50 pointer-events-none" : "hover:brightness-95",
|
|
138
|
+
]);
|
|
139
|
+
</script>
|
|
140
|
+
|
|
141
|
+
<template>
|
|
142
|
+
<component
|
|
143
|
+
:is="resolvedAs"
|
|
144
|
+
:to="to"
|
|
145
|
+
:href="href"
|
|
146
|
+
:target="target"
|
|
147
|
+
:disabled="resolvedAs === 'button' ? isDisabled : undefined"
|
|
148
|
+
:aria-disabled="isDisabled || undefined"
|
|
149
|
+
:class="classes"
|
|
150
|
+
>
|
|
151
|
+
<UiIcon
|
|
152
|
+
v-if="loading"
|
|
153
|
+
name="heroicons:arrow-path"
|
|
154
|
+
class="animate-spin"
|
|
155
|
+
:style="{ color: 'var(--loader-fill-color, currentColor)' }"
|
|
156
|
+
:class="iconSizes[size]"
|
|
157
|
+
/>
|
|
158
|
+
|
|
159
|
+
<UiIcon
|
|
160
|
+
v-else-if="leadingIconName"
|
|
161
|
+
:name="leadingIconName"
|
|
162
|
+
:class="iconSizes[size]"
|
|
163
|
+
/>
|
|
164
|
+
|
|
165
|
+
<span v-if="$slots.default || label">
|
|
166
|
+
<slot>{{ label }}</slot>
|
|
167
|
+
</span>
|
|
168
|
+
|
|
169
|
+
<UiIcon
|
|
170
|
+
v-if="!loading && trailingIconName"
|
|
171
|
+
:name="trailingIconName"
|
|
172
|
+
:class="iconSizes[size]"
|
|
173
|
+
/>
|
|
174
|
+
</component>
|
|
175
|
+
</template>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const props = withDefaults(
|
|
3
|
+
defineProps<{
|
|
4
|
+
as?: "div" | "section" | "article";
|
|
5
|
+
|
|
6
|
+
to?: string | object;
|
|
7
|
+
href?: string;
|
|
8
|
+
target?: string;
|
|
9
|
+
|
|
10
|
+
bordered?: boolean;
|
|
11
|
+
rounded?: boolean;
|
|
12
|
+
padded?: boolean;
|
|
13
|
+
}>(),
|
|
14
|
+
{
|
|
15
|
+
as: "div",
|
|
16
|
+
bordered: true,
|
|
17
|
+
rounded: true,
|
|
18
|
+
padded: true,
|
|
19
|
+
},
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const isLink = computed(() => !!props.to || !!props.href);
|
|
23
|
+
|
|
24
|
+
const tag = computed(() => {
|
|
25
|
+
if (props.to) return "NuxtLink";
|
|
26
|
+
if (props.href) return "a";
|
|
27
|
+
return props.as;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const classes = computed(() => [
|
|
31
|
+
"bg-[var(--ui-bg)] text-[var(--ui-text)] transition",
|
|
32
|
+
props.bordered ? "border border-[var(--ui-border,var(--ui-text))]" : "",
|
|
33
|
+
props.rounded ? "rounded-lg" : "rounded-none",
|
|
34
|
+
isLink.value
|
|
35
|
+
? "cursor-pointer hover:bg-[color-mix(in srgb, var(--ui-bg) 92%, var(--ui-text))] focus:outline-none focus:ring-2 focus:ring-[var(--ui-border,var(--ui-text))] focus:ring-offset-2"
|
|
36
|
+
: "",
|
|
37
|
+
]);
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<template>
|
|
41
|
+
<component
|
|
42
|
+
:is="tag"
|
|
43
|
+
:to="to"
|
|
44
|
+
:href="href"
|
|
45
|
+
:target="target"
|
|
46
|
+
:class="classes"
|
|
47
|
+
>
|
|
48
|
+
<div
|
|
49
|
+
v-if="$slots.header"
|
|
50
|
+
class="px-4 py-3 border-b border-(--ui-border,var(--ui-text))"
|
|
51
|
+
>
|
|
52
|
+
<slot name="header" />
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div :class="padded ? 'p-4' : ''">
|
|
56
|
+
<slot />
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div
|
|
60
|
+
v-if="$slots.footer"
|
|
61
|
+
class="px-4 py-3 border-t border-(--ui-border,var(--ui-text))"
|
|
62
|
+
>
|
|
63
|
+
<slot name="footer" />
|
|
64
|
+
</div>
|
|
65
|
+
</component>
|
|
66
|
+
</template>
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<fieldset>
|
|
3
|
+
<legend class="sr-only">{{ legend || "Checkboxes" }}</legend>
|
|
4
|
+
|
|
5
|
+
<div :class="containerClasses">
|
|
6
|
+
<div :class="wrapperClasses">
|
|
7
|
+
<label
|
|
8
|
+
v-for="option in options"
|
|
9
|
+
:key="option.value"
|
|
10
|
+
:for="getInputId(option.value)"
|
|
11
|
+
class="flex w-full cursor-pointer items-start gap-3 py-3 select-none"
|
|
12
|
+
>
|
|
13
|
+
<input
|
|
14
|
+
:id="getInputId(option.value)"
|
|
15
|
+
type="checkbox"
|
|
16
|
+
:value="option.value"
|
|
17
|
+
:checked="model.includes(option.value)"
|
|
18
|
+
:disabled="option.disabled || disabled"
|
|
19
|
+
@change="toggle(option.value)"
|
|
20
|
+
class="sr-only peer"
|
|
21
|
+
/>
|
|
22
|
+
|
|
23
|
+
<div
|
|
24
|
+
class="relative flex size-5 shrink-0 items-center justify-center rounded border-2 transition-colors duration-200"
|
|
25
|
+
:class="[
|
|
26
|
+
'peer-checked:bg-blue-600 peer-checked:border-blue-600',
|
|
27
|
+
'peer-disabled:opacity-60 peer-disabled:cursor-not-allowed',
|
|
28
|
+
'border-gray-300 dark:border-gray-600',
|
|
29
|
+
'bg-(--ui-bg) mt-0.5',
|
|
30
|
+
]"
|
|
31
|
+
>
|
|
32
|
+
<svg
|
|
33
|
+
class="size-3.5 text-white transition-transform duration-200 origin-center"
|
|
34
|
+
:class="
|
|
35
|
+
model.includes(option.value)
|
|
36
|
+
? 'scale-100'
|
|
37
|
+
: 'scale-0'
|
|
38
|
+
"
|
|
39
|
+
viewBox="0 0 16 16"
|
|
40
|
+
fill="currentColor"
|
|
41
|
+
>
|
|
42
|
+
<path
|
|
43
|
+
d="M13.78 3.22a.75.75 0 0 1 0 1.06l-7.5 7.5a.75.75 0 0 1-1.06 0l-3-3a.75.75 0 1 1 1.06-1.06L6 10.44l7-7a.75.75 0 0 1 1.06 0z"
|
|
44
|
+
/>
|
|
45
|
+
</svg>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div class="flex-1">
|
|
49
|
+
<span
|
|
50
|
+
class="font-medium"
|
|
51
|
+
:style="{ color: 'var(--ui-text)' }"
|
|
52
|
+
>
|
|
53
|
+
{{ option.label }}
|
|
54
|
+
</span>
|
|
55
|
+
<p
|
|
56
|
+
v-if="option.description"
|
|
57
|
+
class="mt-0.5 text-sm opacity-70"
|
|
58
|
+
:style="{ color: 'var(--ui-text)' }"
|
|
59
|
+
>
|
|
60
|
+
{{ option.description }}
|
|
61
|
+
</p>
|
|
62
|
+
</div>
|
|
63
|
+
</label>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</fieldset>
|
|
67
|
+
</template>
|
|
68
|
+
|
|
69
|
+
<script setup lang="ts">
|
|
70
|
+
interface CheckboxOption {
|
|
71
|
+
label: string;
|
|
72
|
+
value: string | number;
|
|
73
|
+
description?: string;
|
|
74
|
+
disabled?: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const props = defineProps({
|
|
78
|
+
modelValue: {
|
|
79
|
+
type: Array as () => (string | number)[],
|
|
80
|
+
default: () => [],
|
|
81
|
+
},
|
|
82
|
+
options: {
|
|
83
|
+
type: Array as () => CheckboxOption[],
|
|
84
|
+
required: true,
|
|
85
|
+
},
|
|
86
|
+
legend: String,
|
|
87
|
+
disabled: Boolean,
|
|
88
|
+
divider: { type: Boolean, default: false },
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const emit = defineEmits<{
|
|
92
|
+
(e: "update:modelValue", value: (string | number)[]): void;
|
|
93
|
+
}>();
|
|
94
|
+
|
|
95
|
+
const idMap = ref<Map<string | number, string>>(new Map());
|
|
96
|
+
let counter = 0;
|
|
97
|
+
|
|
98
|
+
onMounted(() => {
|
|
99
|
+
props.options.forEach((opt) => {
|
|
100
|
+
if (!idMap.value.has(opt.value)) {
|
|
101
|
+
idMap.value.set(opt.value, `cb-${++counter}`);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const getInputId = (value: string | number): string => {
|
|
107
|
+
return idMap.value.get(value) || `cb-${value}`;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const model = computed<(string | number)[]>({
|
|
111
|
+
get: () => props.modelValue,
|
|
112
|
+
set: (val) => emit("update:modelValue", val),
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const toggle = (value: string | number) => {
|
|
116
|
+
const current = [...model.value];
|
|
117
|
+
const index = current.indexOf(value);
|
|
118
|
+
if (index > -1) {
|
|
119
|
+
current.splice(index, 1);
|
|
120
|
+
} else {
|
|
121
|
+
current.push(value);
|
|
122
|
+
}
|
|
123
|
+
emit("update:modelValue", current);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const containerClasses = computed(() =>
|
|
127
|
+
props.divider ? "flow-root" : "space-y-3",
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const wrapperClasses = computed(() => {
|
|
131
|
+
if (!props.divider) return "";
|
|
132
|
+
return "w-full divide-y divide-gray-300 dark:divide-gray-700";
|
|
133
|
+
});
|
|
134
|
+
</script>
|