mirror of
https://github.com/unraid/api.git
synced 2026-01-02 22:50:02 -06:00
Compare commits
13 Commits
v4.9.0
...
feat/notif
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acbd861ce0 | ||
|
|
8ec2358061 | ||
|
|
e7c1c8e7fe | ||
|
|
e933f14c24 | ||
|
|
e6699448a2 | ||
|
|
38aac5e35f | ||
|
|
16a70dcdb0 | ||
|
|
e7b8733648 | ||
|
|
f68727320b | ||
|
|
9c23b9bd1b | ||
|
|
32c0d0be0a | ||
|
|
b9e9a1a2cf | ||
|
|
10cb681d64 |
@@ -1,6 +1,6 @@
|
||||
[api]
|
||||
version="3.8.1+d06e215a"
|
||||
extraOrigins="https://google.com,https://test.com"
|
||||
version="3.11.0+3f537b97"
|
||||
extraOrigins="https://google.com,https://test.com,http://localhost:4321"
|
||||
[local]
|
||||
[notifier]
|
||||
apikey="unnotify_30994bfaccf839c65bae75f7fa12dd5ee16e69389f754c3b98ed7d5"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
[api]
|
||||
version="3.8.1+d06e215a"
|
||||
extraOrigins="https://google.com,https://test.com"
|
||||
version="3.11.0+3f537b97"
|
||||
extraOrigins="https://google.com,https://test.com,http://localhost:4321"
|
||||
[local]
|
||||
[notifier]
|
||||
apikey="unnotify_30994bfaccf839c65bae75f7fa12dd5ee16e69389f754c3b98ed7d5"
|
||||
[remote]
|
||||
wanaccess="no"
|
||||
wanaccess="yes"
|
||||
wanport="8443"
|
||||
upnpEnabled="no"
|
||||
apikey="_______________________BIG_API_KEY_HERE_________________________"
|
||||
@@ -16,8 +16,8 @@ regWizTime="1611175408732_0951-1653-3509-FBA155FA23C0"
|
||||
idtoken=""
|
||||
accesstoken=""
|
||||
refreshtoken=""
|
||||
allowedOrigins="/var/run/unraid-notifications.sock, /var/run/unraid-php.sock, /var/run/unraid-cli.sock, http://localhost:8080, https://localhost:4443, https://tower.local:4443, https://192.168.1.150:4443, https://tower:4443, https://192-168-1-150.thisisfourtyrandomcharacters012345678900.myunraid.net:4443, https://85-121-123-122.thisisfourtyrandomcharacters012345678900.myunraid.net:8443, https://10-252-0-1.hash.myunraid.net:4443, https://10-252-1-1.hash.myunraid.net:4443, https://10-253-3-1.hash.myunraid.net:4443, https://10-253-4-1.hash.myunraid.net:4443, https://10-253-5-1.hash.myunraid.net:4443, https://10-100-0-1.hash.myunraid.net:4443, https://10-100-0-2.hash.myunraid.net:4443, https://10-123-1-2.hash.myunraid.net:4443, https://221-123-121-112.hash.myunraid.net:4443, https://google.com, https://test.com, https://connect.myunraid.net, https://connect-staging.myunraid.net, https://dev-my.myunraid.net:4000, https://studio.apollographql.com"
|
||||
dynamicRemoteAccessType="STATIC"
|
||||
allowedOrigins="/var/run/unraid-notifications.sock, /var/run/unraid-php.sock, /var/run/unraid-cli.sock, http://localhost:8080, https://localhost:4443, https://tower.local:4443, https://192.168.1.150:4443, https://tower:4443, https://192-168-1-150.thisisfourtyrandomcharacters012345678900.myunraid.net:4443, https://85-121-123-122.thisisfourtyrandomcharacters012345678900.myunraid.net:8443, https://10-252-0-1.hash.myunraid.net:4443, https://10-252-1-1.hash.myunraid.net:4443, https://10-253-3-1.hash.myunraid.net:4443, https://10-253-4-1.hash.myunraid.net:4443, https://10-253-5-1.hash.myunraid.net:4443, https://10-100-0-1.hash.myunraid.net:4443, https://10-100-0-2.hash.myunraid.net:4443, https://10-123-1-2.hash.myunraid.net:4443, https://221-123-121-112.hash.myunraid.net:4443, https://google.com, https://test.com, http://localhost:4321, https://connect.myunraid.net, https://connect-staging.myunraid.net, https://dev-my.myunraid.net:4000, https://studio.apollographql.com"
|
||||
dynamicRemoteAccessType="DISABLED"
|
||||
[upc]
|
||||
apikey="unupc_fab6ff6ffe51040595c6d9ffb63a353ba16cc2ad7d93f813a2e80a5810"
|
||||
[connectionStatus]
|
||||
|
||||
@@ -20,6 +20,7 @@ const roles: Record<string, Role> = {
|
||||
{ resource: 'apikey', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'cloud', action: 'read:own', attributes: '*' },
|
||||
{ resource: 'config', action: 'update:own', attributes: '*' },
|
||||
{ resource: 'config', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'connect', action: 'read:own', attributes: '*' },
|
||||
{ resource: 'connect', action: 'update:own', attributes: '*' },
|
||||
{ resource: 'customizations', action: 'read:any', attributes: '*' },
|
||||
@@ -117,6 +118,8 @@ const roles: Record<string, Role> = {
|
||||
{ resource: 'config', action: 'update:own', attributes: '*' },
|
||||
{ resource: 'connect', action: 'read:own', attributes: '*' },
|
||||
{ resource: 'connect', action: 'update:own', attributes: '*' },
|
||||
{ resource: 'notifications', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'notifications', action: 'update:any', attributes: '*' },
|
||||
],
|
||||
},
|
||||
my_servers: {
|
||||
|
||||
@@ -14,6 +14,7 @@ export const GRAPHQL_INTROSPECTION = Boolean(
|
||||
export const PORT = process.env.PORT ?? '/var/run/unraid-api.sock';
|
||||
export const DRY_RUN = process.env.DRY_RUN === 'true';
|
||||
export const BYPASS_PERMISSION_CHECKS = process.env.BYPASS_PERMISSION_CHECKS === 'true';
|
||||
export const BYPASS_CORS_CHECKS = process.env.BYPASS_CORS_CHECKS === 'true';
|
||||
export const LOG_CORS = process.env.LOG_CORS === 'true';
|
||||
export const LOG_TYPE = process.env.LOG_TYPE as 'pretty' | 'raw' ?? 'pretty';
|
||||
export const LOG_LEVEL = process.env.LOG_LEVEL as 'TRACE' | 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'FATAL';
|
||||
|
||||
@@ -12,7 +12,7 @@ import { getAllowedOrigins } from '@app/common/allowed-origins';
|
||||
import { HttpExceptionFilter } from '@app/unraid-api/exceptions/http-exceptions.filter';
|
||||
import { GraphQLError } from 'graphql';
|
||||
import { GraphQLExceptionsFilter } from '@app/unraid-api/exceptions/graphql-exceptions.filter';
|
||||
import { BYPASS_PERMISSION_CHECKS, PORT } from '@app/environment';
|
||||
import { BYPASS_CORS_CHECKS, PORT } from '@app/environment';
|
||||
import { type FastifyInstance } from 'fastify';
|
||||
import { type Server, type IncomingMessage, type ServerResponse } from 'http';
|
||||
import { apiLogger } from '@app/core/log';
|
||||
@@ -25,7 +25,7 @@ export const corsOptionsDelegate: CorsOptionsDelegate = async (
|
||||
} else {
|
||||
apiLogger.debug(`Origin not in allowed origins: ${origin}`);
|
||||
|
||||
if (BYPASS_PERMISSION_CHECKS) {
|
||||
if (BYPASS_CORS_CHECKS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,12 +44,10 @@ import type {
|
||||
// EBLACKLISTED2
|
||||
// ENOCONN
|
||||
|
||||
// '1111-1111-5GDB-123412341234' Starter.key = TkJCrVyXMLWWGKZF6TCEvf0C86UYI9KfUDSOm7JoFP19tOMTMgLKcJ6QIOt9_9Psg_t0yF-ANmzSgZzCo94ljXoPm4BESFByR0K7nyY9KVvU8szLEUcBUT3xC2adxLrAXFNxiPeK-mZqt34n16uETKYvLKL_Sr5_JziG5L5lJFBqYZCPmfLMiguFo1vp0xL8pnBH7q8bYoBnePrAcAVb9mAGxFVPEInSPkMBfC67JLHz7XY1Y_K5bYIq3go9XPtLltJ53_U4BQiMHooXUBJCKXodpqoGxq0eV0IhNEYdauAhnTsG90qmGZig0hZalQ0soouc4JZEMiYEcZbn9mBxPg
|
||||
|
||||
const state: ServerState = "BASIC" as ServerState;
|
||||
const currentFlashGuid = "1111-1111-CFXF-TEST1234ZACK"; // this is the flash drive that's been booted from
|
||||
const regGuid = "1111-1111-CFXF-TEST1234ZACK"; // this guid is registered in key server
|
||||
const keyfileBase64 = "asdf"; // @todo raycast download key to base64
|
||||
const state: ServerState = "ENOKEYFILE2" as ServerState;
|
||||
const currentFlashGuid = "4444-1111-FOUR-999900008888"; // this is the flash drive that's been booted from
|
||||
const regGuid = "4444-1111-FOUR-999900008888"; // this guid is registered in key server
|
||||
const keyfileBase64 = "";
|
||||
|
||||
// const randomGuid = `1111-1111-${makeid(4)}-123412341234`; // this guid is registered in key server
|
||||
// const newGuid = `1234-1234-${makeid(4)}-123412341234`; // this is a new USB, not registered
|
||||
@@ -109,7 +107,7 @@ switch (state) {
|
||||
// const connectPluginInstalled = 'dynamix.unraid.net.staging.plg';
|
||||
const connectPluginInstalled = "";
|
||||
|
||||
const osVersion = "6.12.8";
|
||||
const osVersion = "7.0.0-beta.2.10";
|
||||
const osVersionBranch = "stable";
|
||||
// const parsedRegExp = regExp ? dayjs(regExp).format('YYYY-MM-DD') : undefined;
|
||||
|
||||
@@ -159,8 +157,8 @@ export const serverState: Server = {
|
||||
name: "dev-static",
|
||||
osVersion,
|
||||
osVersionBranch,
|
||||
// registered: connectPluginInstalled ? true : false,
|
||||
registered: false,
|
||||
registered: connectPluginInstalled ? true : false,
|
||||
// registered: false,
|
||||
regGen: 0,
|
||||
regTm: twoDaysAgo,
|
||||
regTo: "Zack Spear",
|
||||
|
||||
@@ -37,3 +37,79 @@ body {
|
||||
|
||||
/* Ensure this is always at the bottom – @see https://tailwindcss.com/docs/content-configuration#working-with-third-party-libraries */
|
||||
@tailwind utilities;
|
||||
|
||||
/* shadcn */
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 0 0% 3.9%;
|
||||
|
||||
--muted: 0 0% 96.1%;
|
||||
--muted-foreground: 0 0% 45.1%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 0 0% 3.9%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 0 0% 3.9%;
|
||||
|
||||
--border: 0 0% 89.8%;
|
||||
--input: 0 0% 89.8%;
|
||||
|
||||
--primary: 0 0% 9%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
|
||||
--secondary: 0 0% 96.1%;
|
||||
--secondary-foreground: 0 0% 9%;
|
||||
|
||||
--accent: 0 0% 96.1%;
|
||||
--accent-foreground: 0 0% 9%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--ring: 0 0% 3.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 0 0% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
|
||||
--muted: 0 0% 14.9%;
|
||||
--muted-foreground: 0 0% 63.9%;
|
||||
|
||||
--popover: 0 0% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
|
||||
--card: 0 0% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
|
||||
--border: 0 0% 14.9%;
|
||||
--input: 0 0% 14.9%;
|
||||
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 0 0% 9%;
|
||||
|
||||
--secondary: 0 0% 14.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
|
||||
--accent: 0 0% 14.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--ring: 0 0% 83.1%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
17
web/components.json
Normal file
17
web/components.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "https://shadcn-vue.com/schema.json",
|
||||
"style": "default",
|
||||
"typescript": true,
|
||||
"tsConfigPath": "tsconfig.json",
|
||||
"tailwind": {
|
||||
"config": "tailwind-shadcn.config.ts",
|
||||
"css": "assets/main.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true
|
||||
},
|
||||
"framework": "nuxt",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/helpers/utils"
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ const { releaseForUpdate: updateOsChangelogModalVisible } = storeToRefs(useUpdat
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative z-[99999]">
|
||||
<div id="modals" class="relative z-[99999]">
|
||||
<UpcCallbackFeedback :t="t" :open="callbackStatus !== 'ready'" />
|
||||
<UpcTrial :t="t" :open="trialModalVisible" />
|
||||
<UpdateOsCheckUpdateResponseModal :t="t" :open="updateOsModalVisible" />
|
||||
|
||||
72
web/components/Notifications/Item.vue
Normal file
72
web/components/Notifications/Item.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ArchiveBoxIcon,
|
||||
ShieldExclamationIcon,
|
||||
CheckBadgeIcon,
|
||||
ExclamationTriangleIcon,
|
||||
ChevronRightIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
|
||||
import type { NotificationItemProps } from '~/types/ui/notification';
|
||||
|
||||
const props = defineProps<NotificationItemProps>();
|
||||
|
||||
const icon = computed<{ component: Component, color: string } | null>(() => {
|
||||
switch (props.importance) {
|
||||
case 'INFO':
|
||||
return {
|
||||
component: CheckBadgeIcon,
|
||||
color: 'text-green-500',
|
||||
};
|
||||
case 'WARNING':
|
||||
return {
|
||||
component: ExclamationTriangleIcon,
|
||||
color: 'text-yellow-500',
|
||||
};
|
||||
case 'ALERT':
|
||||
return {
|
||||
component: ShieldExclamationIcon,
|
||||
color: 'text-red-500',
|
||||
};
|
||||
}
|
||||
return null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="group/item relative w-full py-4 flex flex-col gap-2">
|
||||
<header class="w-full flex flex-row items-start justify-between gap-2">
|
||||
<h3 class="text-16px font-semibold leading-2 flex flex-row items-start gap-2">
|
||||
<component :is="icon.component" v-if="icon" class="size-6 shrink-0" :class="icon.color" />
|
||||
<span>{{ title }} • {{ subject }}</span>
|
||||
</h3>
|
||||
|
||||
<div class="shrink-0 flex flex-row items-center justify-end gap-2 mt-1">
|
||||
<p class="text-12px opacity-75">{{ timestamp }}</p>
|
||||
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<button class="relative z-20">
|
||||
<span class="sr-only">Archive</span>
|
||||
<ArchiveBoxIcon class="size-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Archive</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="w-full flex flex-row items-center justify-between gap-2 opacity-75 group-hover/item:opacity-100 group-focus/item:opacity-100">
|
||||
<p>{{ description }}</p>
|
||||
<ChevronRightIcon class="size-4" />
|
||||
</div>
|
||||
|
||||
<a :href="link" class="absolute z-10 inset-0">
|
||||
<span class="sr-only">Take me there</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
12
web/components/Notifications/OpenButton.vue
Normal file
12
web/components/Notifications/OpenButton.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { useNotificationsStore } from '~/store/notifications';
|
||||
|
||||
const notificationsStore = useNotificationsStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BrandButton text="My Button" @click="notificationsStore.toggle" />
|
||||
|
||||
<NotificationsSidebar />
|
||||
</template>
|
||||
|
||||
122
web/components/Notifications/Sidebar.vue
Normal file
122
web/components/Notifications/Sidebar.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<script setup lang="ts">
|
||||
import { BellIcon } from "@heroicons/vue/24/solid";
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetFooter,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from "@/components/shadcn/sheet";
|
||||
|
||||
import type { NotificationItemProps } from "~/types/ui/notification";
|
||||
import { useUnraidApiStore } from "~/store/unraidApi";
|
||||
import gql from "graphql-tag";
|
||||
|
||||
const getNotifications = gql`
|
||||
query Notifications($filter: NotificationFilter!) {
|
||||
notifications {
|
||||
list(filter: $filter) {
|
||||
id
|
||||
title
|
||||
subject
|
||||
description
|
||||
importance
|
||||
link
|
||||
type
|
||||
timestamp
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const notifications = ref<NotificationItemProps[]>([]);
|
||||
watch(notifications, (newVal) => {
|
||||
console.log('[notifications]', newVal);
|
||||
});
|
||||
|
||||
const fetchType = ref<'UNREAD' | 'ARCHIVED'>('UNREAD');
|
||||
const setFetchType = (type: 'UNREAD' | 'ARCHIVED') => fetchType.value = type;
|
||||
|
||||
const { unraidApiClient } = storeToRefs(useUnraidApiStore());
|
||||
|
||||
watch(unraidApiClient, async(newVal) => {
|
||||
if (newVal) {
|
||||
const apiResponse = await newVal.query({
|
||||
query: getNotifications,
|
||||
variables: {
|
||||
filter: {
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
type: fetchType.value,
|
||||
},
|
||||
},
|
||||
});
|
||||
notifications.value = apiResponse.data.notifications.list;
|
||||
}
|
||||
});
|
||||
|
||||
const { teleportTarget, determineTeleportTarget } = useTeleport();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Sheet>
|
||||
<SheetTrigger @click="determineTeleportTarget">
|
||||
<span class="sr-only">Notifications</span>
|
||||
<BellIcon class="w-6 h-6" />
|
||||
</SheetTrigger>
|
||||
|
||||
<SheetContent :to="teleportTarget" class="w-full max-w-[400px] sm:max-w-[540px]">
|
||||
<SheetHeader>
|
||||
<SheetTitle>Notifications</SheetTitle>
|
||||
</SheetHeader>
|
||||
|
||||
<div class="flex flex-row justify-between items-center">
|
||||
<div class="w-auto flex flex-row justify-start items-center gap-1 p-2 rounded">
|
||||
<Button
|
||||
v-for="opt in ['Unread', 'Archived']"
|
||||
:key="opt"
|
||||
:variant="fetchType === opt ? 'secondary' : undefined"
|
||||
class="py-2 px-4 text-left"
|
||||
@click="setFetchType(opt.toUpperCase() as 'UNREAD' | 'ARCHIVED')"
|
||||
>
|
||||
{{ opt }}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="w-auto flex flex-row justify-start items-center gap-1 p-2 rounded">
|
||||
<Button
|
||||
variant="secondary"
|
||||
class="py-2 px-4 text-left"
|
||||
>
|
||||
{{ `Archive All` }}
|
||||
</Button>
|
||||
<Select>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Filter" />
|
||||
</SelectTrigger>
|
||||
<SelectContent :to="teleportTarget">
|
||||
<SelectGroup>
|
||||
<SelectLabel>Notification Types</SelectLabel>
|
||||
<SelectItem value="alert">Alert</SelectItem>
|
||||
<SelectItem value="info">Info</SelectItem>
|
||||
<SelectItem value="warning">Warning</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divide-y divide-gray-200">
|
||||
<NotificationsItem
|
||||
v-for="notification in notifications"
|
||||
:key="notification.id"
|
||||
v-bind="notification"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SheetFooter class="text-center">
|
||||
<p>Future pagination station</p>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</template>
|
||||
@@ -127,6 +127,8 @@ onBeforeMount(() => {
|
||||
|
||||
<div class="block w-2px h-24px bg-gamma" />
|
||||
|
||||
<!-- <NotificationsSidebar /> -->
|
||||
|
||||
<OnClickOutside class="flex items-center justify-end h-full" :options="{ ignore: [clickOutsideIgnoreTarget] }" @trigger="outsideDropdown">
|
||||
<UpcDropdownTrigger ref="clickOutsideIgnoreTarget" :t="t" />
|
||||
<UpcDropdown ref="clickOutsideTarget" :t="t" />
|
||||
|
||||
@@ -23,7 +23,6 @@ const showExternalIconOnHover = computed(() => props.item?.external && props.ite
|
||||
:is="item?.click ? 'button' : 'a'"
|
||||
:disabled="item?.disabled"
|
||||
:href="item?.href ?? null"
|
||||
:title="item?.title ? t(item?.title) : null"
|
||||
:target="item?.external ? '_blank' : null"
|
||||
:rel="item?.external ? 'noopener noreferrer' : null"
|
||||
class="text-left text-14px w-full flex flex-row items-center justify-between gap-x-8px px-8px py-8px cursor-pointer"
|
||||
|
||||
26
web/components/shadcn/button/Button.vue
Normal file
26
web/components/shadcn/button/Button.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { Primitive, type PrimitiveProps } from 'radix-vue'
|
||||
import { type ButtonVariants, buttonVariants } from '.'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
interface Props extends PrimitiveProps {
|
||||
variant?: ButtonVariants['variant']
|
||||
size?: ButtonVariants['size']
|
||||
class?: HTMLAttributes['class']
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
as: 'button',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive
|
||||
:as="as"
|
||||
:as-child="asChild"
|
||||
:class="cn(buttonVariants({ variant, size }), props.class)"
|
||||
>
|
||||
<slot />
|
||||
</Primitive>
|
||||
</template>
|
||||
35
web/components/shadcn/button/index.ts
Normal file
35
web/components/shadcn/button/index.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { type VariantProps, cva } from 'class-variance-authority'
|
||||
|
||||
export { default as Button } from './Button.vue'
|
||||
|
||||
export const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
destructive:
|
||||
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||
outline:
|
||||
'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
||||
secondary:
|
||||
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
},
|
||||
size: {
|
||||
default: 'h-10 px-4 py-2',
|
||||
xs: 'h-7 rounded px-2',
|
||||
sm: 'h-9 rounded-md px-3',
|
||||
lg: 'h-11 rounded-md px-8',
|
||||
icon: 'h-10 w-10',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
export type ButtonVariants = VariantProps<typeof buttonVariants>
|
||||
14
web/components/shadcn/dropdown-menu/DropdownMenu.vue
Normal file
14
web/components/shadcn/dropdown-menu/DropdownMenu.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { DropdownMenuRoot, type DropdownMenuRootEmits, type DropdownMenuRootProps, useForwardPropsEmits } from 'radix-vue'
|
||||
|
||||
const props = defineProps<DropdownMenuRootProps>()
|
||||
const emits = defineEmits<DropdownMenuRootEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuRoot v-bind="forwarded">
|
||||
<slot />
|
||||
</DropdownMenuRoot>
|
||||
</template>
|
||||
@@ -0,0 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import {
|
||||
DropdownMenuCheckboxItem,
|
||||
type DropdownMenuCheckboxItemEmits,
|
||||
type DropdownMenuCheckboxItemProps,
|
||||
DropdownMenuItemIndicator,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
import { Check } from 'lucide-vue-next'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<DropdownMenuCheckboxItemProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<DropdownMenuCheckboxItemEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuCheckboxItem
|
||||
v-bind="forwarded"
|
||||
:class=" cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuItemIndicator>
|
||||
<Check class="w-4 h-4" />
|
||||
</DropdownMenuItemIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
</DropdownMenuCheckboxItem>
|
||||
</template>
|
||||
38
web/components/shadcn/dropdown-menu/DropdownMenuContent.vue
Normal file
38
web/components/shadcn/dropdown-menu/DropdownMenuContent.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import {
|
||||
DropdownMenuContent,
|
||||
type DropdownMenuContentEmits,
|
||||
type DropdownMenuContentProps,
|
||||
DropdownMenuPortal,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<DropdownMenuContentProps & { class?: HTMLAttributes['class'] }>(),
|
||||
{
|
||||
sideOffset: 4,
|
||||
},
|
||||
)
|
||||
const emits = defineEmits<DropdownMenuContentEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuContent
|
||||
v-bind="forwarded"
|
||||
:class="cn('z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenuPortal>
|
||||
</template>
|
||||
11
web/components/shadcn/dropdown-menu/DropdownMenuGroup.vue
Normal file
11
web/components/shadcn/dropdown-menu/DropdownMenuGroup.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { DropdownMenuGroup, type DropdownMenuGroupProps } from 'radix-vue'
|
||||
|
||||
const props = defineProps<DropdownMenuGroupProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuGroup v-bind="props">
|
||||
<slot />
|
||||
</DropdownMenuGroup>
|
||||
</template>
|
||||
28
web/components/shadcn/dropdown-menu/DropdownMenuItem.vue
Normal file
28
web/components/shadcn/dropdown-menu/DropdownMenuItem.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { DropdownMenuItem, type DropdownMenuItemProps, useForwardProps } from 'radix-vue'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<DropdownMenuItemProps & { class?: HTMLAttributes['class'], inset?: boolean }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuItem
|
||||
v-bind="forwardedProps"
|
||||
:class="cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
inset && 'pl-8',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<slot />
|
||||
</DropdownMenuItem>
|
||||
</template>
|
||||
24
web/components/shadcn/dropdown-menu/DropdownMenuLabel.vue
Normal file
24
web/components/shadcn/dropdown-menu/DropdownMenuLabel.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { DropdownMenuLabel, type DropdownMenuLabelProps, useForwardProps } from 'radix-vue'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<DropdownMenuLabelProps & { class?: HTMLAttributes['class'], inset?: boolean }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuLabel
|
||||
v-bind="forwardedProps"
|
||||
:class="cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</DropdownMenuLabel>
|
||||
</template>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
DropdownMenuRadioGroup,
|
||||
type DropdownMenuRadioGroupEmits,
|
||||
type DropdownMenuRadioGroupProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
|
||||
const props = defineProps<DropdownMenuRadioGroupProps>()
|
||||
const emits = defineEmits<DropdownMenuRadioGroupEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuRadioGroup v-bind="forwarded">
|
||||
<slot />
|
||||
</DropdownMenuRadioGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import {
|
||||
DropdownMenuItemIndicator,
|
||||
DropdownMenuRadioItem,
|
||||
type DropdownMenuRadioItemEmits,
|
||||
type DropdownMenuRadioItemProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
import { Circle } from 'lucide-vue-next'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<DropdownMenuRadioItemProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const emits = defineEmits<DropdownMenuRadioItemEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuRadioItem
|
||||
v-bind="forwarded"
|
||||
:class="cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuItemIndicator>
|
||||
<Circle class="h-2 w-2 fill-current" />
|
||||
</DropdownMenuItemIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
</DropdownMenuRadioItem>
|
||||
</template>
|
||||
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import {
|
||||
DropdownMenuSeparator,
|
||||
type DropdownMenuSeparatorProps,
|
||||
} from 'radix-vue'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<DropdownMenuSeparatorProps & {
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuSeparator v-bind="delegatedProps" :class="cn('-mx-1 my-1 h-px bg-muted', props.class)" />
|
||||
</template>
|
||||
14
web/components/shadcn/dropdown-menu/DropdownMenuShortcut.vue
Normal file
14
web/components/shadcn/dropdown-menu/DropdownMenuShortcut.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span :class="cn('ml-auto text-xs tracking-widest opacity-60', props.class)">
|
||||
<slot />
|
||||
</span>
|
||||
</template>
|
||||
19
web/components/shadcn/dropdown-menu/DropdownMenuSub.vue
Normal file
19
web/components/shadcn/dropdown-menu/DropdownMenuSub.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
DropdownMenuSub,
|
||||
type DropdownMenuSubEmits,
|
||||
type DropdownMenuSubProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
|
||||
const props = defineProps<DropdownMenuSubProps>()
|
||||
const emits = defineEmits<DropdownMenuSubEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuSub v-bind="forwarded">
|
||||
<slot />
|
||||
</DropdownMenuSub>
|
||||
</template>
|
||||
@@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import {
|
||||
DropdownMenuSubContent,
|
||||
type DropdownMenuSubContentEmits,
|
||||
type DropdownMenuSubContentProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<DropdownMenuSubContentProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<DropdownMenuSubContentEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuSubContent
|
||||
v-bind="forwarded"
|
||||
:class="cn('z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</DropdownMenuSubContent>
|
||||
</template>
|
||||
@@ -0,0 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import {
|
||||
DropdownMenuSubTrigger,
|
||||
type DropdownMenuSubTriggerProps,
|
||||
useForwardProps,
|
||||
} from 'radix-vue'
|
||||
import { ChevronRight } from 'lucide-vue-next'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<DropdownMenuSubTriggerProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuSubTrigger
|
||||
v-bind="forwardedProps"
|
||||
:class="cn(
|
||||
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<slot />
|
||||
<ChevronRight class="ml-auto h-4 w-4" />
|
||||
</DropdownMenuSubTrigger>
|
||||
</template>
|
||||
13
web/components/shadcn/dropdown-menu/DropdownMenuTrigger.vue
Normal file
13
web/components/shadcn/dropdown-menu/DropdownMenuTrigger.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { DropdownMenuTrigger, type DropdownMenuTriggerProps, useForwardProps } from 'radix-vue'
|
||||
|
||||
const props = defineProps<DropdownMenuTriggerProps>()
|
||||
|
||||
const forwardedProps = useForwardProps(props)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuTrigger class="outline-none" v-bind="forwardedProps">
|
||||
<slot />
|
||||
</DropdownMenuTrigger>
|
||||
</template>
|
||||
16
web/components/shadcn/dropdown-menu/index.ts
Normal file
16
web/components/shadcn/dropdown-menu/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export { DropdownMenuPortal } from 'radix-vue'
|
||||
|
||||
export { default as DropdownMenu } from './DropdownMenu.vue'
|
||||
export { default as DropdownMenuTrigger } from './DropdownMenuTrigger.vue'
|
||||
export { default as DropdownMenuContent } from './DropdownMenuContent.vue'
|
||||
export { default as DropdownMenuGroup } from './DropdownMenuGroup.vue'
|
||||
export { default as DropdownMenuRadioGroup } from './DropdownMenuRadioGroup.vue'
|
||||
export { default as DropdownMenuItem } from './DropdownMenuItem.vue'
|
||||
export { default as DropdownMenuCheckboxItem } from './DropdownMenuCheckboxItem.vue'
|
||||
export { default as DropdownMenuRadioItem } from './DropdownMenuRadioItem.vue'
|
||||
export { default as DropdownMenuShortcut } from './DropdownMenuShortcut.vue'
|
||||
export { default as DropdownMenuSeparator } from './DropdownMenuSeparator.vue'
|
||||
export { default as DropdownMenuLabel } from './DropdownMenuLabel.vue'
|
||||
export { default as DropdownMenuSub } from './DropdownMenuSub.vue'
|
||||
export { default as DropdownMenuSubTrigger } from './DropdownMenuSubTrigger.vue'
|
||||
export { default as DropdownMenuSubContent } from './DropdownMenuSubContent.vue'
|
||||
24
web/components/shadcn/input/Input.vue
Normal file
24
web/components/shadcn/input/Input.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
defaultValue?: string | number
|
||||
modelValue?: string | number
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'update:modelValue', payload: string | number): void
|
||||
}>()
|
||||
|
||||
const modelValue = useVModel(props, 'modelValue', emits, {
|
||||
passive: true,
|
||||
defaultValue: props.defaultValue,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-model="modelValue" :class="cn('flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', props.class)">
|
||||
</template>
|
||||
1
web/components/shadcn/input/index.ts
Normal file
1
web/components/shadcn/input/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Input } from './Input.vue'
|
||||
27
web/components/shadcn/label/Label.vue
Normal file
27
web/components/shadcn/label/Label.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { Label, type LabelProps } from 'radix-vue'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<LabelProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Label
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</Label>
|
||||
</template>
|
||||
1
web/components/shadcn/label/index.ts
Normal file
1
web/components/shadcn/label/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Label } from './Label.vue'
|
||||
15
web/components/shadcn/select/Select.vue
Normal file
15
web/components/shadcn/select/Select.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import type { SelectRootEmits, SelectRootProps } from 'radix-vue'
|
||||
import { SelectRoot, useForwardPropsEmits } from 'radix-vue'
|
||||
|
||||
const props = defineProps<SelectRootProps>()
|
||||
const emits = defineEmits<SelectRootEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SelectRoot v-bind="forwarded">
|
||||
<slot />
|
||||
</SelectRoot>
|
||||
</template>
|
||||
58
web/components/shadcn/select/SelectContent.vue
Normal file
58
web/components/shadcn/select/SelectContent.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import {
|
||||
SelectContent,
|
||||
type SelectContentEmits,
|
||||
type SelectContentProps,
|
||||
SelectPortal,
|
||||
SelectViewport,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
import { SelectScrollDownButton, SelectScrollUpButton } from '.'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<SelectContentProps & {
|
||||
class?: HTMLAttributes['class']
|
||||
disabled?: boolean
|
||||
forceMount?: boolean
|
||||
to?: string | HTMLElement | Element
|
||||
}>(),
|
||||
{
|
||||
position: 'popper',
|
||||
},
|
||||
)
|
||||
const emits = defineEmits<SelectContentEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SelectPortal :disabled="disabled" :force-mount="forceMount" :to="to">
|
||||
<SelectContent
|
||||
v-bind="{ ...forwarded, ...$attrs }" :class="cn(
|
||||
'relative z-50 max-h-96 min-w-32 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
position === 'popper'
|
||||
&& 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectViewport :class="cn('p-1', position === 'popper' && 'h-[--radix-select-trigger-height] w-full min-w-[--radix-select-trigger-width]')">
|
||||
<slot />
|
||||
</SelectViewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectContent>
|
||||
</SelectPortal>
|
||||
</template>
|
||||
19
web/components/shadcn/select/SelectGroup.vue
Normal file
19
web/components/shadcn/select/SelectGroup.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { SelectGroup, type SelectGroupProps } from 'radix-vue'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<SelectGroupProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SelectGroup :class="cn('p-1 w-full', props.class)" v-bind="delegatedProps">
|
||||
<slot />
|
||||
</SelectGroup>
|
||||
</template>
|
||||
44
web/components/shadcn/select/SelectItem.vue
Normal file
44
web/components/shadcn/select/SelectItem.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import {
|
||||
SelectItem,
|
||||
SelectItemIndicator,
|
||||
type SelectItemProps,
|
||||
SelectItemText,
|
||||
useForwardProps,
|
||||
} from 'radix-vue'
|
||||
import { Check } from 'lucide-vue-next'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<SelectItemProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SelectItem
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectItemIndicator>
|
||||
<Check class="h-4 w-4" />
|
||||
</SelectItemIndicator>
|
||||
</span>
|
||||
|
||||
<SelectItemText>
|
||||
<slot />
|
||||
</SelectItemText>
|
||||
</SelectItem>
|
||||
</template>
|
||||
11
web/components/shadcn/select/SelectItemText.vue
Normal file
11
web/components/shadcn/select/SelectItemText.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { SelectItemText, type SelectItemTextProps } from 'radix-vue'
|
||||
|
||||
const props = defineProps<SelectItemTextProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SelectItemText v-bind="props">
|
||||
<slot />
|
||||
</SelectItemText>
|
||||
</template>
|
||||
13
web/components/shadcn/select/SelectLabel.vue
Normal file
13
web/components/shadcn/select/SelectLabel.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { SelectLabel, type SelectLabelProps } from 'radix-vue'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<SelectLabelProps & { class?: HTMLAttributes['class'] }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SelectLabel :class="cn('py-1.5 pl-8 pr-2 text-sm font-semibold', props.class)">
|
||||
<slot />
|
||||
</SelectLabel>
|
||||
</template>
|
||||
24
web/components/shadcn/select/SelectScrollDownButton.vue
Normal file
24
web/components/shadcn/select/SelectScrollDownButton.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { SelectScrollDownButton, type SelectScrollDownButtonProps, useForwardProps } from 'radix-vue'
|
||||
import { ChevronDown } from 'lucide-vue-next'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<SelectScrollDownButtonProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SelectScrollDownButton v-bind="forwardedProps" :class="cn('flex cursor-default items-center justify-center py-1', props.class)">
|
||||
<slot>
|
||||
<ChevronDown class="h-4 w-4" />
|
||||
</slot>
|
||||
</SelectScrollDownButton>
|
||||
</template>
|
||||
24
web/components/shadcn/select/SelectScrollUpButton.vue
Normal file
24
web/components/shadcn/select/SelectScrollUpButton.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { SelectScrollUpButton, type SelectScrollUpButtonProps, useForwardProps } from 'radix-vue'
|
||||
import { ChevronUp } from 'lucide-vue-next'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<SelectScrollUpButtonProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SelectScrollUpButton v-bind="forwardedProps" :class="cn('flex cursor-default items-center justify-center py-1', props.class)">
|
||||
<slot>
|
||||
<ChevronUp class="h-4 w-4" />
|
||||
</slot>
|
||||
</SelectScrollUpButton>
|
||||
</template>
|
||||
17
web/components/shadcn/select/SelectSeparator.vue
Normal file
17
web/components/shadcn/select/SelectSeparator.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { SelectSeparator, type SelectSeparatorProps } from 'radix-vue'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<SelectSeparatorProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SelectSeparator v-bind="delegatedProps" :class="cn('-mx-1 my-1 h-px bg-muted', props.class)" />
|
||||
</template>
|
||||
31
web/components/shadcn/select/SelectTrigger.vue
Normal file
31
web/components/shadcn/select/SelectTrigger.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { SelectIcon, SelectTrigger, type SelectTriggerProps, useForwardProps } from 'radix-vue'
|
||||
import { ChevronDown } from 'lucide-vue-next'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<SelectTriggerProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SelectTrigger
|
||||
v-bind="forwardedProps"
|
||||
:class="cn(
|
||||
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:truncate text-start',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<slot />
|
||||
<SelectIcon as-child>
|
||||
<ChevronDown class="w-4 h-4 opacity-50 shrink-0" />
|
||||
</SelectIcon>
|
||||
</SelectTrigger>
|
||||
</template>
|
||||
11
web/components/shadcn/select/SelectValue.vue
Normal file
11
web/components/shadcn/select/SelectValue.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { SelectValue, type SelectValueProps } from 'radix-vue'
|
||||
|
||||
const props = defineProps<SelectValueProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SelectValue v-bind="props">
|
||||
<slot />
|
||||
</SelectValue>
|
||||
</template>
|
||||
11
web/components/shadcn/select/index.ts
Normal file
11
web/components/shadcn/select/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export { default as Select } from './Select.vue'
|
||||
export { default as SelectValue } from './SelectValue.vue'
|
||||
export { default as SelectTrigger } from './SelectTrigger.vue'
|
||||
export { default as SelectContent } from './SelectContent.vue'
|
||||
export { default as SelectGroup } from './SelectGroup.vue'
|
||||
export { default as SelectItem } from './SelectItem.vue'
|
||||
export { default as SelectItemText } from './SelectItemText.vue'
|
||||
export { default as SelectLabel } from './SelectLabel.vue'
|
||||
export { default as SelectSeparator } from './SelectSeparator.vue'
|
||||
export { default as SelectScrollUpButton } from './SelectScrollUpButton.vue'
|
||||
export { default as SelectScrollDownButton } from './SelectScrollDownButton.vue'
|
||||
14
web/components/shadcn/sheet/Sheet.vue
Normal file
14
web/components/shadcn/sheet/Sheet.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { DialogRoot, type DialogRootEmits, type DialogRootProps, useForwardPropsEmits } from 'radix-vue'
|
||||
|
||||
const props = defineProps<DialogRootProps>()
|
||||
const emits = defineEmits<DialogRootEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogRoot v-bind="forwarded">
|
||||
<slot />
|
||||
</DialogRoot>
|
||||
</template>
|
||||
11
web/components/shadcn/sheet/SheetClose.vue
Normal file
11
web/components/shadcn/sheet/SheetClose.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { DialogClose, type DialogCloseProps } from 'radix-vue'
|
||||
|
||||
const props = defineProps<DialogCloseProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogClose v-bind="props">
|
||||
<slot />
|
||||
</DialogClose>
|
||||
</template>
|
||||
59
web/components/shadcn/sheet/SheetContent.vue
Normal file
59
web/components/shadcn/sheet/SheetContent.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import {
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
type DialogContentEmits,
|
||||
type DialogContentProps,
|
||||
DialogOverlay,
|
||||
DialogPortal,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
import { X } from 'lucide-vue-next'
|
||||
import { type SheetVariants, sheetVariants } from '.'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
interface SheetContentProps extends DialogContentProps {
|
||||
class?: HTMLAttributes['class']
|
||||
side?: SheetVariants['side']
|
||||
disabled?: boolean
|
||||
forceMount?: boolean
|
||||
to?: string | HTMLElement | Element
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
const props = defineProps<SheetContentProps>()
|
||||
|
||||
const emits = defineEmits<DialogContentEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, side, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogPortal :disabled="disabled" :force-mount="forceMount" :to="to">
|
||||
<DialogOverlay
|
||||
class="fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
|
||||
/>
|
||||
<DialogContent
|
||||
:class="cn(sheetVariants({ side }), props.class)"
|
||||
v-bind="{ ...forwarded, ...$attrs }"
|
||||
>
|
||||
<slot />
|
||||
|
||||
<DialogClose
|
||||
class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary"
|
||||
>
|
||||
<X class="w-4 h-4 text-muted-foreground" />
|
||||
</DialogClose>
|
||||
</DialogContent>
|
||||
</DialogPortal>
|
||||
</template>
|
||||
22
web/components/shadcn/sheet/SheetDescription.vue
Normal file
22
web/components/shadcn/sheet/SheetDescription.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { DialogDescription, type DialogDescriptionProps } from 'radix-vue'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogDescription
|
||||
:class="cn('text-sm text-muted-foreground', props.class)"
|
||||
v-bind="delegatedProps"
|
||||
>
|
||||
<slot />
|
||||
</DialogDescription>
|
||||
</template>
|
||||
19
web/components/shadcn/sheet/SheetFooter.vue
Normal file
19
web/components/shadcn/sheet/SheetFooter.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<{ class?: HTMLAttributes['class'] }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-x-2',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
16
web/components/shadcn/sheet/SheetHeader.vue
Normal file
16
web/components/shadcn/sheet/SheetHeader.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<{ class?: HTMLAttributes['class'] }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
cn('flex flex-col gap-y-2 text-center sm:text-left', props.class)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
22
web/components/shadcn/sheet/SheetTitle.vue
Normal file
22
web/components/shadcn/sheet/SheetTitle.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { DialogTitle, type DialogTitleProps } from 'radix-vue'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<DialogTitleProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogTitle
|
||||
:class="cn('text-lg font-semibold text-foreground', props.class)"
|
||||
v-bind="delegatedProps"
|
||||
>
|
||||
<slot />
|
||||
</DialogTitle>
|
||||
</template>
|
||||
11
web/components/shadcn/sheet/SheetTrigger.vue
Normal file
11
web/components/shadcn/sheet/SheetTrigger.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { DialogTrigger, type DialogTriggerProps } from 'radix-vue'
|
||||
|
||||
const props = defineProps<DialogTriggerProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogTrigger v-bind="props">
|
||||
<slot />
|
||||
</DialogTrigger>
|
||||
</template>
|
||||
31
web/components/shadcn/sheet/index.ts
Normal file
31
web/components/shadcn/sheet/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { type VariantProps, cva } from 'class-variance-authority'
|
||||
|
||||
export { default as Sheet } from './Sheet.vue'
|
||||
export { default as SheetTrigger } from './SheetTrigger.vue'
|
||||
export { default as SheetClose } from './SheetClose.vue'
|
||||
export { default as SheetContent } from './SheetContent.vue'
|
||||
export { default as SheetHeader } from './SheetHeader.vue'
|
||||
export { default as SheetTitle } from './SheetTitle.vue'
|
||||
export { default as SheetDescription } from './SheetDescription.vue'
|
||||
export { default as SheetFooter } from './SheetFooter.vue'
|
||||
|
||||
export const sheetVariants = cva(
|
||||
'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
|
||||
{
|
||||
variants: {
|
||||
side: {
|
||||
top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
|
||||
bottom:
|
||||
'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
|
||||
left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
|
||||
right:
|
||||
'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
side: 'right',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
export type SheetVariants = VariantProps<typeof sheetVariants>
|
||||
37
web/components/shadcn/switch/Switch.vue
Normal file
37
web/components/shadcn/switch/Switch.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import {
|
||||
SwitchRoot,
|
||||
type SwitchRootEmits,
|
||||
type SwitchRootProps,
|
||||
SwitchThumb,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<SwitchRootProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const emits = defineEmits<SwitchRootEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SwitchRoot
|
||||
v-bind="forwarded"
|
||||
:class="cn(
|
||||
'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<SwitchThumb
|
||||
:class="cn('pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0')"
|
||||
/>
|
||||
</SwitchRoot>
|
||||
</template>
|
||||
1
web/components/shadcn/switch/index.ts
Normal file
1
web/components/shadcn/switch/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Switch } from './Switch.vue'
|
||||
15
web/components/shadcn/tabs/Tabs.vue
Normal file
15
web/components/shadcn/tabs/Tabs.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { TabsRoot, useForwardPropsEmits } from 'radix-vue'
|
||||
import type { TabsRootEmits, TabsRootProps } from 'radix-vue'
|
||||
|
||||
const props = defineProps<TabsRootProps>()
|
||||
const emits = defineEmits<TabsRootEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TabsRoot v-bind="forwarded">
|
||||
<slot />
|
||||
</TabsRoot>
|
||||
</template>
|
||||
22
web/components/shadcn/tabs/TabsContent.vue
Normal file
22
web/components/shadcn/tabs/TabsContent.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { TabsContent, type TabsContentProps } from 'radix-vue'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<TabsContentProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TabsContent
|
||||
:class="cn('mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', props.class)"
|
||||
v-bind="delegatedProps"
|
||||
>
|
||||
<slot />
|
||||
</TabsContent>
|
||||
</template>
|
||||
25
web/components/shadcn/tabs/TabsList.vue
Normal file
25
web/components/shadcn/tabs/TabsList.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { TabsList, type TabsListProps } from 'radix-vue'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<TabsListProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TabsList
|
||||
v-bind="delegatedProps"
|
||||
:class="cn(
|
||||
'inline-flex items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<slot />
|
||||
</TabsList>
|
||||
</template>
|
||||
29
web/components/shadcn/tabs/TabsTrigger.vue
Normal file
29
web/components/shadcn/tabs/TabsTrigger.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { TabsTrigger, type TabsTriggerProps, useForwardProps } from 'radix-vue'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps<TabsTriggerProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TabsTrigger
|
||||
v-bind="forwardedProps"
|
||||
:class="cn(
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<span class="truncate">
|
||||
<slot />
|
||||
</span>
|
||||
</TabsTrigger>
|
||||
</template>
|
||||
4
web/components/shadcn/tabs/index.ts
Normal file
4
web/components/shadcn/tabs/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as Tabs } from './Tabs.vue'
|
||||
export { default as TabsTrigger } from './TabsTrigger.vue'
|
||||
export { default as TabsList } from './TabsList.vue'
|
||||
export { default as TabsContent } from './TabsContent.vue'
|
||||
14
web/components/shadcn/tooltip/Tooltip.vue
Normal file
14
web/components/shadcn/tooltip/Tooltip.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { TooltipRoot, type TooltipRootEmits, type TooltipRootProps, useForwardPropsEmits } from 'radix-vue'
|
||||
|
||||
const props = defineProps<TooltipRootProps>()
|
||||
const emits = defineEmits<TooltipRootEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TooltipRoot v-bind="forwarded">
|
||||
<slot />
|
||||
</TooltipRoot>
|
||||
</template>
|
||||
33
web/components/shadcn/tooltip/TooltipContent.vue
Normal file
33
web/components/shadcn/tooltip/TooltipContent.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from 'vue'
|
||||
import { TooltipContent, type TooltipContentEmits, type TooltipContentProps, TooltipPortal, useForwardPropsEmits } from 'radix-vue'
|
||||
import { cn } from '@/helpers/utils'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
const props = withDefaults(defineProps<TooltipContentProps & { class?: HTMLAttributes['class'] }>(), {
|
||||
sideOffset: 4,
|
||||
})
|
||||
|
||||
const emits = defineEmits<TooltipContentEmits>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
|
||||
return delegated
|
||||
})
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
|
||||
const { teleportTarget } = useTeleport();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TooltipPortal :to="teleportTarget" defer>
|
||||
<TooltipContent v-bind="{ ...forwarded, ...$attrs }" :class="cn('z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', props.class)">
|
||||
<slot />
|
||||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
</template>
|
||||
11
web/components/shadcn/tooltip/TooltipProvider.vue
Normal file
11
web/components/shadcn/tooltip/TooltipProvider.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { TooltipProvider, type TooltipProviderProps } from 'radix-vue'
|
||||
|
||||
const props = defineProps<TooltipProviderProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TooltipProvider v-bind="props">
|
||||
<slot />
|
||||
</TooltipProvider>
|
||||
</template>
|
||||
11
web/components/shadcn/tooltip/TooltipTrigger.vue
Normal file
11
web/components/shadcn/tooltip/TooltipTrigger.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { TooltipTrigger, type TooltipTriggerProps } from 'radix-vue'
|
||||
|
||||
const props = defineProps<TooltipTriggerProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TooltipTrigger v-bind="props">
|
||||
<slot />
|
||||
</TooltipTrigger>
|
||||
</template>
|
||||
4
web/components/shadcn/tooltip/index.ts
Normal file
4
web/components/shadcn/tooltip/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as Tooltip } from './Tooltip.vue'
|
||||
export { default as TooltipContent } from './TooltipContent.vue'
|
||||
export { default as TooltipTrigger } from './TooltipTrigger.vue'
|
||||
export { default as TooltipProvider } from './TooltipProvider.vue'
|
||||
27
web/composables/useTeleport.ts
Normal file
27
web/composables/useTeleport.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { ref, onMounted } from "vue";
|
||||
|
||||
const useTeleport = () => {
|
||||
const teleportTarget = ref<string | HTMLElement | Element>("#modals");
|
||||
|
||||
const determineTeleportTarget = () => {
|
||||
const myModalsComponent = document.querySelector("unraid-modals");
|
||||
if (!myModalsComponent?.shadowRoot) return;
|
||||
|
||||
const potentialTarget = myModalsComponent.shadowRoot.querySelector("#modals");
|
||||
if (!potentialTarget) return;
|
||||
|
||||
teleportTarget.value = potentialTarget;
|
||||
console.log("[determineTeleportTarget] teleportTarget", teleportTarget.value);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
determineTeleportTarget();
|
||||
});
|
||||
|
||||
return {
|
||||
teleportTarget,
|
||||
determineTeleportTarget,
|
||||
};
|
||||
};
|
||||
|
||||
export default useTeleport;
|
||||
@@ -2,7 +2,7 @@
|
||||
export const OBJ_TO_STR = (obj: object): string => Object.entries(obj).reduce((str, [p, val]) => `${str}${p}: ${val}\n`, '');
|
||||
/** Removes our dev logs from prod builds */
|
||||
export const disableProductionConsoleLogs = () => {
|
||||
if (import.meta.env.PROD && !import.meta.env.VITE_ALLOW_CONSOLE_LOGS) {
|
||||
if (import.meta.env.PROD && import.meta.env.VITE_ALLOW_CONSOLE_LOGS !== 'true') {
|
||||
console.log = () => {};
|
||||
console.debug = () => {};
|
||||
console.info = () => {};
|
||||
|
||||
6
web/helpers/utils.ts
Normal file
6
web/helpers/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
@@ -17,7 +17,7 @@ console.log('\n');
|
||||
* @see alt solution https://github.com/terser/terser/issues/1001, https://github.com/terser/terser/pull/1038
|
||||
*/
|
||||
function terserReservations (inputStr: string) {
|
||||
const combinations = ['ace'];
|
||||
const combinations = ['ace', 'i'];
|
||||
|
||||
// Add 1-character combinations
|
||||
for (let i = 0; i < inputStr.length; i++) {
|
||||
@@ -50,11 +50,13 @@ export default defineNuxtConfig({
|
||||
'@pinia/nuxt',
|
||||
'@nuxtjs/tailwindcss',
|
||||
'nuxt-custom-elements',
|
||||
"@nuxt/eslint"
|
||||
"@nuxt/eslint",
|
||||
'shadcn-nuxt',
|
||||
],
|
||||
components: [
|
||||
{ path: '~/components/Brand', prefix: 'Brand' },
|
||||
{ path: '~/components/ConnectSettings', prefix: 'ConnectSettings' },
|
||||
{ path: '~/components/Notifications', prefix: 'Notifications' },
|
||||
{ path: '~/components/Ui', prefix: 'Ui' },
|
||||
{ path: '~/components/UserProfile', prefix: 'Upc' },
|
||||
{ path: '~/components/UpdateOs', prefix: 'UpdateOs' },
|
||||
@@ -63,16 +65,20 @@ export default defineNuxtConfig({
|
||||
// typescript: {
|
||||
// typeCheck: true
|
||||
// },
|
||||
shadcn: {
|
||||
prefix: '',
|
||||
componentDir: './components/shadcn'
|
||||
},
|
||||
vite: {
|
||||
build: {
|
||||
minify: 'terser',
|
||||
terserOptions: {
|
||||
mangle: process.env.VITE_ALLOW_CONSOLE_LOGS
|
||||
? false
|
||||
: {
|
||||
reserved: terserReservations(charsToReserve),
|
||||
toplevel: true,
|
||||
},
|
||||
mangle: process.env.VITE_ALLOW_CONSOLE_LOGS !== 'true'
|
||||
? {
|
||||
reserved: terserReservations(charsToReserve),
|
||||
toplevel: true,
|
||||
}
|
||||
: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
864
web/package-lock.json
generated
864
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -30,27 +30,34 @@
|
||||
"@graphql-codegen/introspection": "^4.0.3",
|
||||
"@nuxt/devtools": "^1.3.1",
|
||||
"@nuxt/eslint": "^0.3.12",
|
||||
"@nuxtjs/tailwindcss": "^6.12.0",
|
||||
"@nuxtjs/tailwindcss": "^6.12.1",
|
||||
"@tailwindcss/typography": "^0.5.13",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/node": "^18",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@vue/apollo-util": "^4.0.0-beta.6",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"@vueuse/core": "^10.11.1",
|
||||
"@vueuse/nuxt": "^10.9.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nuxt": "^3.11.2",
|
||||
"nuxt-custom-elements": "^2.0.0-beta.18",
|
||||
"shadcn-nuxt": "^0.10.4",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"terser": "^5.31.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.10.4",
|
||||
"@floating-ui/dom": "^1.6.11",
|
||||
"@floating-ui/utils": "^0.2.8",
|
||||
"@floating-ui/vue": "^1.1.5",
|
||||
"@headlessui/vue": "^1.7.22",
|
||||
"@heroicons/vue": "^2.1.3",
|
||||
"@pinia/nuxt": "^0.5.1",
|
||||
"@vue/apollo-composable": "^4.0.2",
|
||||
"@vueuse/components": "^10.9.0",
|
||||
"@vueuse/integrations": "^10.9.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.11",
|
||||
"focus-trap": "^7.5.4",
|
||||
@@ -58,13 +65,19 @@
|
||||
"graphql-tag": "^2.12.6",
|
||||
"graphql-ws": "^5.16.0",
|
||||
"hex-to-rgba": "^2.0.1",
|
||||
"lucide-vue-next": "^0.445.0",
|
||||
"marked": "^12.0.2",
|
||||
"marked-base-url": "^1.1.3",
|
||||
"radix-vue": "^1.9.6",
|
||||
"semver": "^7.6.2",
|
||||
"vue-i18n": "^9.13.1",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"vue-i18n": "^9.14.1",
|
||||
"wretch": "^2.8.1"
|
||||
},
|
||||
"overrides": {
|
||||
"vue": "latest"
|
||||
"vue": "latest",
|
||||
"radix-vue": {
|
||||
"@floating-ui/vue": "^1.1.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ onMounted(() => {
|
||||
</div>
|
||||
<UserProfileCe :server="serverState" />
|
||||
</header>
|
||||
<hr class="border-black dark:border-white">
|
||||
<!-- <hr class="border-black dark:border-white"> -->
|
||||
|
||||
<h3 class="text-lg font-semibold font-mono">
|
||||
ConnectSettingsCe
|
||||
|
||||
28
web/store/notifications.ts
Normal file
28
web/store/notifications.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { defineStore, createPinia, setActivePinia } from 'pinia';
|
||||
import type { NotificationItemProps } from "~/types/ui/notification";
|
||||
|
||||
setActivePinia(createPinia());
|
||||
|
||||
export const useNotificationsStore = defineStore('notifications', () => {
|
||||
const notifications = ref<NotificationItemProps[]>([]);
|
||||
const isOpen = ref<boolean>(false);
|
||||
|
||||
const title = computed<string>(() => isOpen.value ? 'Notifications Are Open' : 'Notifications Are Closed');
|
||||
|
||||
const toggle = () => isOpen.value = !isOpen.value;
|
||||
|
||||
const setNotifications = (newNotifications: NotificationItemProps[]) => {
|
||||
notifications.value = newNotifications;
|
||||
};
|
||||
|
||||
return {
|
||||
// state
|
||||
isOpen,
|
||||
// getters
|
||||
title,
|
||||
notifications,
|
||||
// actions
|
||||
setNotifications,
|
||||
toggle,
|
||||
};
|
||||
});
|
||||
86
web/tailwind-shadcn.config.ts
Normal file
86
web/tailwind-shadcn.config.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
const animate = require("tailwindcss-animate")
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: ["class"],
|
||||
safelist: ["dark"],
|
||||
prefix: "",
|
||||
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
xl: "calc(var(--radius) + 4px)",
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: 0 },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: 0 },
|
||||
},
|
||||
"collapsible-down": {
|
||||
from: { height: 0 },
|
||||
to: { height: 'var(--radix-collapsible-content-height)' },
|
||||
},
|
||||
"collapsible-up": {
|
||||
from: { height: 'var(--radix-collapsible-content-height)' },
|
||||
to: { height: 0 },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
"collapsible-down": "collapsible-down 0.2s ease-in-out",
|
||||
"collapsible-up": "collapsible-up 0.2s ease-in-out",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [animate],
|
||||
}
|
||||
@@ -5,8 +5,11 @@ import { readFileSync } from 'fs';
|
||||
import { parse } from 'dotenv';
|
||||
const envConfig = parse(readFileSync('.env'));
|
||||
|
||||
// @ts-expect-error - just trying to get this to build @fixme
|
||||
export default <Partial<Config>>{
|
||||
darkMode: ["class"],
|
||||
safelist: [
|
||||
"dark",
|
||||
"DropdownWrapper_blip",
|
||||
"unraid_mark_1",
|
||||
"unraid_mark_2",
|
||||
@@ -18,6 +21,13 @@ export default <Partial<Config>>{
|
||||
"unraid_mark_9",
|
||||
],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: "clear-sans,ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji",
|
||||
@@ -45,6 +55,41 @@ export default <Partial<Config>>{
|
||||
beta: "var(--color-beta)",
|
||||
gamma: "var(--color-gamma)",
|
||||
"gamma-opaque": "var(--color-gamma-opaque)",
|
||||
|
||||
// shadcn specific
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
// Unfortunately due to webGUI CSS setting base HTML font-size to .65% or something we must use pixel values for web components
|
||||
fontSize: {
|
||||
@@ -113,6 +158,36 @@ export default <Partial<Config>>{
|
||||
xs: "530px",
|
||||
tall: { raw: "(min-height: 700px)" },
|
||||
},
|
||||
borderRadius: {
|
||||
xl: "calc(var(--radius) + 4px)",
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: 0 },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: 0 },
|
||||
},
|
||||
"collapsible-down": {
|
||||
from: { height: 0 },
|
||||
to: { height: 'var(--radix-collapsible-content-height)' },
|
||||
},
|
||||
"collapsible-up": {
|
||||
from: { height: 'var(--radix-collapsible-content-height)' },
|
||||
to: { height: 0 },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
"collapsible-down": "collapsible-down 0.2s ease-in-out",
|
||||
"collapsible-up": "collapsible-up 0.2s ease-in-out",
|
||||
},
|
||||
/**
|
||||
* @todo modify prose classes to use pixels for webgui…sadge https://tailwindcss.com/docs/typography-plugin#customizing-the-default-theme
|
||||
*/
|
||||
@@ -202,14 +277,18 @@ export default <Partial<Config>>{
|
||||
}),
|
||||
},
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
plugins: [require("@tailwindcss/typography"), require('./utils/tailwind-rem-to-rem').default({
|
||||
baseFontSize: 16,
|
||||
/**
|
||||
* The font size where the web components will rendered in production.
|
||||
* Required due to the webgui using the 62.5% font-size "trick".
|
||||
* Set an env to 16 for local development and 10 for everything else.
|
||||
*/
|
||||
newFontSize: envConfig.VITE_TAILWIND_BASE_FONT_SIZE ?? 10,
|
||||
})],
|
||||
};
|
||||
plugins: [
|
||||
require("@tailwindcss/typography"),
|
||||
require("tailwindcss-animate"),
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
require('./utils/tailwind-rem-to-rem').default({
|
||||
baseFontSize: 16,
|
||||
/**
|
||||
* The font size where the web components will be rendered in production.
|
||||
* Required due to the webgui using the 62.5% font-size "trick".
|
||||
* Set an env to 16 for local development and 10 for everything else.
|
||||
*/
|
||||
newFontSize: envConfig.VITE_TAILWIND_BASE_FONT_SIZE ?? 10,
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
10
web/types/ui/notification.ts
Normal file
10
web/types/ui/notification.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export interface NotificationItemProps {
|
||||
id: string;
|
||||
title: string;
|
||||
subject: string;
|
||||
description: string;
|
||||
importance: string;
|
||||
link: string;
|
||||
type: 'success' | 'warning' | 'alert';
|
||||
timestamp: string;
|
||||
}
|
||||
Reference in New Issue
Block a user