mirror of
https://github.com/versia-pub/frontend.git
synced 2026-03-13 03:29:16 +01:00
refactor: ♻️ Rewrite entire preferences panel and emoji manager from scratch
This commit is contained in:
parent
0443a37508
commit
17bb75733c
51 changed files with 1982 additions and 17 deletions
|
|
@ -35,5 +35,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|||
<Check class="size-3.5" />
|
||||
</slot>
|
||||
</CheckboxIndicator>
|
||||
<!-- Fixes an issue where empty buttons behave weirdly in tanstack table layouts -->
|
||||
<Check class="size-3.5 opacity-0" />
|
||||
</CheckboxRoot>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'bg-background 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 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
|
||||
'bg-background 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 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] max-h-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
|
|
|
|||
25
components/ui/number-field/NumberField.vue
Normal file
25
components/ui/number-field/NumberField.vue
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<script setup lang="ts">
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { NumberFieldRootEmits, NumberFieldRootProps } from "reka-ui";
|
||||
import { NumberFieldRoot, useForwardPropsEmits } from "reka-ui";
|
||||
import { type HTMLAttributes, computed } from "vue";
|
||||
|
||||
const props = defineProps<
|
||||
NumberFieldRootProps & { class?: HTMLAttributes["class"] }
|
||||
>();
|
||||
const emits = defineEmits<NumberFieldRootEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NumberFieldRoot v-bind="forwarded" :class="cn('grid gap-1.5', props.class)">
|
||||
<slot />
|
||||
</NumberFieldRoot>
|
||||
</template>
|
||||
14
components/ui/number-field/NumberFieldContent.vue
Normal file
14
components/ui/number-field/NumberFieldContent.vue
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<script setup lang="ts">
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('relative [&>[data-slot=input]]:has-[[data-slot=increment]]:pr-5 [&>[data-slot=input]]:has-[[data-slot=decrement]]:pl-5', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
27
components/ui/number-field/NumberFieldDecrement.vue
Normal file
27
components/ui/number-field/NumberFieldDecrement.vue
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<script setup lang="ts">
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Minus } from "lucide-vue-next";
|
||||
import type { NumberFieldDecrementProps } from "reka-ui";
|
||||
import { NumberFieldDecrement, useForwardProps } from "reka-ui";
|
||||
import { type HTMLAttributes, computed } from "vue";
|
||||
|
||||
const props = defineProps<
|
||||
NumberFieldDecrementProps & { class?: HTMLAttributes["class"] }
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NumberFieldDecrement data-slot="decrement" v-bind="forwarded" :class="cn('absolute top-1/2 -translate-y-1/2 left-0 p-3 disabled:cursor-not-allowed disabled:opacity-20', props.class)">
|
||||
<slot>
|
||||
<Minus class="h-4 w-4" />
|
||||
</slot>
|
||||
</NumberFieldDecrement>
|
||||
</template>
|
||||
27
components/ui/number-field/NumberFieldIncrement.vue
Normal file
27
components/ui/number-field/NumberFieldIncrement.vue
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<script setup lang="ts">
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Plus } from "lucide-vue-next";
|
||||
import type { NumberFieldIncrementProps } from "reka-ui";
|
||||
import { NumberFieldIncrement, useForwardProps } from "reka-ui";
|
||||
import { type HTMLAttributes, computed } from "vue";
|
||||
|
||||
const props = defineProps<
|
||||
NumberFieldIncrementProps & { class?: HTMLAttributes["class"] }
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NumberFieldIncrement data-slot="increment" v-bind="forwarded" :class="cn('absolute top-1/2 -translate-y-1/2 right-0 disabled:cursor-not-allowed disabled:opacity-20 p-3', props.class)">
|
||||
<slot>
|
||||
<Plus class="h-4 w-4" />
|
||||
</slot>
|
||||
</NumberFieldIncrement>
|
||||
</template>
|
||||
16
components/ui/number-field/NumberFieldInput.vue
Normal file
16
components/ui/number-field/NumberFieldInput.vue
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<script setup lang="ts">
|
||||
import { cn } from "@/lib/utils";
|
||||
import { NumberFieldInput } from "reka-ui";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NumberFieldInput
|
||||
data-slot="input"
|
||||
:class="cn('flex h-9 w-full rounded-md border border-input bg-transparent py-1 text-sm text-center shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50', props.class)"
|
||||
/>
|
||||
</template>
|
||||
5
components/ui/number-field/index.ts
Normal file
5
components/ui/number-field/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export { default as NumberField } from "./NumberField.vue";
|
||||
export { default as NumberFieldContent } from "./NumberFieldContent.vue";
|
||||
export { default as NumberFieldDecrement } from "./NumberFieldDecrement.vue";
|
||||
export { default as NumberFieldIncrement } from "./NumberFieldIncrement.vue";
|
||||
export { default as NumberFieldInput } from "./NumberFieldInput.vue";
|
||||
|
|
@ -29,7 +29,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|||
data-slot="switch"
|
||||
v-bind="forwarded"
|
||||
:class="cn(
|
||||
'peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] hover:cursor-pointer disabled:cursor-not-allowed disabled:opacity-50',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
|
|
|
|||
16
components/ui/table/Table.vue
Normal file
16
components/ui/table/Table.vue
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<script setup lang="ts">
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div data-slot="table-container" class="relative w-full overflow-auto">
|
||||
<table data-slot="table" :class="cn('w-full caption-bottom text-sm', props.class)">
|
||||
<slot />
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
17
components/ui/table/TableBody.vue
Normal file
17
components/ui/table/TableBody.vue
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<script setup lang="ts">
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tbody
|
||||
data-slot="table-body"
|
||||
:class="cn('[&_tr:last-child]:border-0', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</tbody>
|
||||
</template>
|
||||
17
components/ui/table/TableCaption.vue
Normal file
17
components/ui/table/TableCaption.vue
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<script setup lang="ts">
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<caption
|
||||
data-slot="table-caption"
|
||||
:class="cn('text-muted-foreground mt-4 text-sm', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</caption>
|
||||
</template>
|
||||
22
components/ui/table/TableCell.vue
Normal file
22
components/ui/table/TableCell.vue
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<script setup lang="ts">
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<td
|
||||
data-slot="table-cell"
|
||||
:class="
|
||||
cn(
|
||||
'p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</td>
|
||||
</template>
|
||||
40
components/ui/table/TableEmpty.vue
Normal file
40
components/ui/table/TableEmpty.vue
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<script setup lang="ts">
|
||||
import { cn } from "@/lib/utils";
|
||||
import { type HTMLAttributes, computed } from "vue";
|
||||
import TableCell from "./TableCell.vue";
|
||||
import TableRow from "./TableRow.vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
class?: HTMLAttributes["class"];
|
||||
colspan?: number;
|
||||
}>(),
|
||||
{
|
||||
colspan: 1,
|
||||
},
|
||||
);
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableRow>
|
||||
<TableCell
|
||||
:class="
|
||||
cn(
|
||||
'p-4 whitespace-nowrap align-middle text-sm text-foreground',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
v-bind="delegatedProps"
|
||||
>
|
||||
<div class="flex items-center justify-center py-10">
|
||||
<slot />
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</template>
|
||||
17
components/ui/table/TableFooter.vue
Normal file
17
components/ui/table/TableFooter.vue
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<script setup lang="ts">
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tfoot
|
||||
data-slot="table-footer"
|
||||
:class="cn('bg-muted/50 border-t font-medium [&>tr]:last:border-b-0', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</tfoot>
|
||||
</template>
|
||||
17
components/ui/table/TableHead.vue
Normal file
17
components/ui/table/TableHead.vue
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<script setup lang="ts">
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<th
|
||||
data-slot="table-head"
|
||||
:class="cn('text-muted-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</th>
|
||||
</template>
|
||||
17
components/ui/table/TableHeader.vue
Normal file
17
components/ui/table/TableHeader.vue
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<script setup lang="ts">
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<thead
|
||||
data-slot="table-header"
|
||||
:class="cn('[&_tr]:border-b', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</thead>
|
||||
</template>
|
||||
17
components/ui/table/TableRow.vue
Normal file
17
components/ui/table/TableRow.vue
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<script setup lang="ts">
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr
|
||||
data-slot="table-row"
|
||||
:class="cn('hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</tr>
|
||||
</template>
|
||||
9
components/ui/table/index.ts
Normal file
9
components/ui/table/index.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export { default as Table } from "./Table.vue";
|
||||
export { default as TableBody } from "./TableBody.vue";
|
||||
export { default as TableCaption } from "./TableCaption.vue";
|
||||
export { default as TableCell } from "./TableCell.vue";
|
||||
export { default as TableEmpty } from "./TableEmpty.vue";
|
||||
export { default as TableFooter } from "./TableFooter.vue";
|
||||
export { default as TableHead } from "./TableHead.vue";
|
||||
export { default as TableHeader } from "./TableHeader.vue";
|
||||
export { default as TableRow } from "./TableRow.vue";
|
||||
12
components/ui/table/utils.ts
Normal file
12
components/ui/table/utils.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import type { Updater } from "@tanstack/vue-table";
|
||||
import type { Ref } from "vue";
|
||||
|
||||
export function valueUpdater<T extends Updater<any>>(
|
||||
updaterOrValue: T,
|
||||
ref: Ref,
|
||||
) {
|
||||
ref.value =
|
||||
typeof updaterOrValue === "function"
|
||||
? updaterOrValue(ref.value)
|
||||
: updaterOrValue;
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ const delegatedProps = computed(() => {
|
|||
data-slot="tabs-list"
|
||||
v-bind="delegatedProps"
|
||||
:class="cn(
|
||||
'bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]',
|
||||
'bg-muted text-muted-foreground inline-flex w-fit items-center justify-center rounded-lg p-1 overflow-x-auto',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ const forwardedProps = useForwardProps(delegatedProps);
|
|||
data-slot="tabs-trigger"
|
||||
v-bind="forwardedProps"
|
||||
:class="cn(
|
||||
`data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4`,
|
||||
`data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4`,
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue