mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-15 12:39:35 +01:00
Compare commits
73 Commits
v3.0.0-alp
...
v3.0.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68f0269046 | ||
|
|
47d9955ed9 | ||
|
|
6386a4d99a | ||
|
|
7687ac16fd | ||
|
|
90a775bab9 | ||
|
|
efeb3f9cfa | ||
|
|
b54950e3ed | ||
|
|
34bddd45be | ||
|
|
69d7b57825 | ||
|
|
92873e05ba | ||
|
|
7870288367 | ||
|
|
9d3d5db376 | ||
|
|
090fe16cff | ||
|
|
44ebb35953 | ||
|
|
5000a4e0d5 | ||
|
|
5385944359 | ||
|
|
7802aacf3f | ||
|
|
f59844bb61 | ||
|
|
9359603a0a | ||
|
|
d407c42be7 | ||
|
|
8a06981df2 | ||
|
|
a68016ec5d | ||
|
|
973023a04e | ||
|
|
f6789a156c | ||
|
|
61b232377b | ||
|
|
eb1b30db40 | ||
|
|
b975235c8b | ||
|
|
81a59969f6 | ||
|
|
dc8cd1e664 | ||
|
|
c63920d05b | ||
|
|
37171b9327 | ||
|
|
49abad243c | ||
|
|
c1294f6505 | ||
|
|
53a3796d1b | ||
|
|
df2013ca92 | ||
|
|
0666884b6f | ||
|
|
a54c3e49fe | ||
|
|
716ed10068 | ||
|
|
e137577a72 | ||
|
|
0f9349f920 | ||
|
|
e6143e8600 | ||
|
|
b9380c15ab | ||
|
|
0b7f171268 | ||
|
|
490fb1377b | ||
|
|
8ef6e712ac | ||
|
|
0759e29c22 | ||
|
|
5385f84e0a | ||
|
|
46bd1cb002 | ||
|
|
8258cd3829 | ||
|
|
16b48efa96 | ||
|
|
b39c4d127e | ||
|
|
6af276ef38 | ||
|
|
67fe33f820 | ||
|
|
9e8b9dcc62 | ||
|
|
e9affb6f5b | ||
|
|
6e9f6a8ef4 | ||
|
|
dcce571cda | ||
|
|
ea07dffdd5 | ||
|
|
acfc6cef2d | ||
|
|
f6f9823b15 | ||
|
|
296ae456c9 | ||
|
|
f6631ff7bc | ||
|
|
bcfa4b74a9 | ||
|
|
1a7af6d182 | ||
|
|
558871a46a | ||
|
|
c8c17490ab | ||
|
|
9e03da41b3 | ||
|
|
a2bad2eee2 | ||
|
|
7c21ddefa8 | ||
|
|
0f9ac8733e | ||
|
|
365bc0fc9a | ||
|
|
c34a805e5f | ||
|
|
b4ffcedd2e |
65
.github/ISSUE_TEMPLATE/bug-v3.yml
vendored
Normal file
65
.github/ISSUE_TEMPLATE/bug-v3.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
name: "🐛 Bug report (v3)"
|
||||
description: Report a bug to help us improve the module (v3 only).
|
||||
labels: ["triage", "bug", "v3"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
> [!IMPORTANT]
|
||||
> As Nuxt UI v3 is currently in alpha, we recommend thorough testing before using it in production environments. We're actively working on stabilization and welcome feedback from early adopters to improve the library.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before reporting a bug, please make sure that you have read through our [v3 documentation](https://ui3.nuxt.dev/) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3).
|
||||
- type: textarea
|
||||
id: env
|
||||
attributes:
|
||||
label: Environment
|
||||
description: You can use `npx nuxi info` to fill this section
|
||||
placeholder: |
|
||||
- Operating System: `Darwin`
|
||||
- Node Version: `v18.16.0`
|
||||
- Nuxt Version: `3.7.3`
|
||||
- CLI Version: `3.8.4`
|
||||
- Nitro Version: `2.6.3`
|
||||
- Package Manager: `pnpm@8.7.4`
|
||||
- Builder: `-`
|
||||
- User Config: `-`
|
||||
- Runtime Modules: `-`
|
||||
- Build Modules: `-`
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
placeholder: v3.0.0-alpha.5
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Reproduction
|
||||
description: Please provide a reproduction link. A minimal [reproduction is required](https://antfu.me/posts/why-reproductions-are-required) unless you are absolutely sure that the issue is obvious and the provided information is enough to understand the problem. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "needs reproduction" label. If no reproduction is provided we might close it.
|
||||
placeholder: https://github.com/my/reproduction
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additonal
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: If applicable, add any other context or screenshots here.
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs
|
||||
description: |
|
||||
Optional if provided reproduction. Please try not to insert an image but copy paste the log text.
|
||||
render: shell-script
|
||||
9
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
9
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -6,6 +6,15 @@ body:
|
||||
attributes:
|
||||
value: |
|
||||
Before requesting a feature, please make sure that you have read through our [documentation](https://ui.nuxt.com) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc).
|
||||
- type: dropdown
|
||||
id: version
|
||||
attributes:
|
||||
label: For what version of Nuxt UI are you suggesting this?
|
||||
options:
|
||||
- v2.x
|
||||
- v3-alpha
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
|
||||
9
.github/ISSUE_TEMPLATE/question.yml
vendored
9
.github/ISSUE_TEMPLATE/question.yml
vendored
@@ -6,6 +6,15 @@ body:
|
||||
attributes:
|
||||
value: |
|
||||
Before asking a question, please make sure that you have read through our [documentation](https://ui.nuxt.com) and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc).
|
||||
- type: dropdown
|
||||
id: version
|
||||
attributes:
|
||||
label: For what version of Nuxt UI are you asking this question?
|
||||
options:
|
||||
- v2.x
|
||||
- v3-alpha
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
|
||||
4
.github/workflows/ci-v3.yml
vendored
4
.github/workflows/ci-v3.yml
vendored
@@ -49,6 +49,10 @@ jobs:
|
||||
- name: Typecheck
|
||||
run: pnpm run typecheck
|
||||
|
||||
- name: Docs typecheck
|
||||
run: pnpm run docs:typecheck
|
||||
continue-on-error: true
|
||||
|
||||
- name: Test
|
||||
run: pnpm run test
|
||||
|
||||
|
||||
35
CHANGELOG.md
35
CHANGELOG.md
@@ -1,5 +1,40 @@
|
||||
# Changelog
|
||||
|
||||
## [3.0.0-alpha.7](https://github.com/nuxt/ui/compare/v3.0.0-alpha.6...v3.0.0-alpha.7) (2024-10-23)
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **components:** rename `select` to `onSelect` on items
|
||||
|
||||
### Features
|
||||
|
||||
* **Accordion/Breadcrumb/CommandPalette/ContextMenu/DropdownMenu/NavigationMenu/Tabs:** add `labelKey` prop ([acfc6ce](https://github.com/nuxt/ui/commit/acfc6cef2db88774749d38a98416fdd85922d513))
|
||||
* **Button:** handle `avatar` prop ([a54c3e4](https://github.com/nuxt/ui/commit/a54c3e49fe782e329f9245e496c336143e3e4b23))
|
||||
* **CommandPalette:** handle `loading` field in items ([49abad2](https://github.com/nuxt/ui/commit/49abad243cee97b99753e2500c4bdaa0efe5eb75))
|
||||
* **ContextMenu/DropdownMenu:** handle `checkbox` items type ([8ef6e71](https://github.com/nuxt/ui/commit/8ef6e712acbb2fc026eb35cefa8e29fc0b59d70f)), closes [#2144](https://github.com/nuxt/ui/issues/2144)
|
||||
* **ContextMenu/DropdownMenu:** handle `loading` field in items ([b975235](https://github.com/nuxt/ui/commit/b975235c8b8e693a32efd3fd5381eed88fa3db4d))
|
||||
* **Form:** add `superstruct` validation ([#2363](https://github.com/nuxt/ui/issues/2363)) ([5385944](https://github.com/nuxt/ui/commit/53859443593b584f7cd44106021e80f441e9ca06))
|
||||
* **Input/InputMenu/Select/SelectMenu:** handle `avatar` prop ([53a3796](https://github.com/nuxt/ui/commit/53a3796d1b08717a589028f99fc01084df661708))
|
||||
* **InputMenu/RadioGroup/Select/SelectMenu:** handle `labelKey` and use `get` to support dot notation ([f6f9823](https://github.com/nuxt/ui/commit/f6f9823b15d84362d093703cb15ecba64c73c2c2))
|
||||
* **NavigationMenu:** handle children on `vertical` orientation ([#2384](https://github.com/nuxt/ui/issues/2384)) ([34bddd4](https://github.com/nuxt/ui/commit/34bddd45be2ba1d51ddb9b6b40860f2414f63180))
|
||||
* **Table:** implement component ([#2364](https://github.com/nuxt/ui/issues/2364)) ([b54950e](https://github.com/nuxt/ui/commit/b54950e3ed77a466eb048788757a76018638eafa))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **AvatarGroup:** wrong ring on big sizes ([61b2323](https://github.com/nuxt/ui/commit/61b232377b4b1fb41de30fd33e690a36b36ba575))
|
||||
* **Button:** invalid hover on `link` variant ([df2013c](https://github.com/nuxt/ui/commit/df2013ca92a49b5947e2fbc2641fd92860c32042))
|
||||
* **Checkbox:** `indeterminate` prop not working ([f6631ff](https://github.com/nuxt/ui/commit/f6631ff7bc607e140e9db2c7335c409a811820e4))
|
||||
* **components:** rename `select` to `onSelect` on items ([b39c4d1](https://github.com/nuxt/ui/commit/b39c4d127e0ddf7607e868ecc83930ca49436bad))
|
||||
* **css:** `font-sans` already applied on <html> ([9e03da4](https://github.com/nuxt/ui/commit/9e03da41b3537236864ae2a533c47e99a6270b77))
|
||||
* **css:** make `[@theme](https://github.com/theme)` default ([a2bad2e](https://github.com/nuxt/ui/commit/a2bad2eee2d2a9255152692898078d26e9ecad98))
|
||||
* **Drawer/Modal/Slideover:** no need for `z-index` since its isolated ([bcfa4b7](https://github.com/nuxt/ui/commit/bcfa4b74a9713be764ecb6db93d60d1360e52f07)), closes [nuxt/ui#2347](https://github.com/nuxt/ui/issues/2347)
|
||||
* **Input/InputMenu/Select/SelectMenu:** uniformize placeholder color ([f59844b](https://github.com/nuxt/ui/commit/f59844bb617f50ef78ae5abe250b0744d7341a2f))
|
||||
* **InputMenu/SelectMenu:** escape regexp before search ([7c21dde](https://github.com/nuxt/ui/commit/7c21ddefa87bf3d9999c0e790b48c004c078304d))
|
||||
* **InputMenu/SelectMenu:** improve displayed value ([0f9ac87](https://github.com/nuxt/ui/commit/0f9ac8733e402d1f22a3eb6c1e24a8d5607b3572)), closes [nuxt/ui#2353](https://github.com/nuxt/ui/issues/2353)
|
||||
* **InputMenu:** emit `focus` event ([#2386](https://github.com/nuxt/ui/issues/2386)) ([7802aac](https://github.com/nuxt/ui/commit/7802aacf3f5be572dd64c3288196432a41f06b0e))
|
||||
* **module:** stop using tailwind's shorthand arbitrary variable syntax ([#2366](https://github.com/nuxt/ui/issues/2366)) ([dcce571](https://github.com/nuxt/ui/commit/dcce571cdab08de8408c8ba6b236b051eec3a603))
|
||||
* **Slideover:** set max height on `top` / `bottom` positions ([a68016e](https://github.com/nuxt/ui/commit/a68016ec5d6859e892c90333d35fd7db09fdcf10)), closes [nuxt/ui#2388](https://github.com/nuxt/ui/issues/2388)
|
||||
|
||||
## [3.0.0-alpha.6](https://github.com/nuxt/ui/compare/v3.0.0-alpha.5...v3.0.0-alpha.6) (2024-10-09)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<UBanner icon="i-heroicons-wrench-screwdriver" :actions="[{ label: 'Go to Nuxt UI v2', to: 'https://ui.nuxt.com', trailingIcon: 'i-heroicons-arrow-right-20-solid', class: 'rounded-full' }]" :close="false">
|
||||
<UBanner icon="i-heroicons-wrench-screwdriver" :actions="[{ label: 'Go to Nuxt UI v2', to: 'https://ui.nuxt.com', trailingIcon: 'i-heroicons-arrow-right-20-solid' }]" :close="false">
|
||||
<template #title>
|
||||
You're looking at the documentation for <span class="font-semibold">Nuxt UI v3</span>!
|
||||
</template>
|
||||
|
||||
@@ -23,11 +23,11 @@ const route = useRoute()
|
||||
|
||||
<UFooter>
|
||||
<template #left>
|
||||
<NuxtLink v-if="route.path.startsWith('/pro')" to="https://ui.nuxt.com/pro/purchase" target="_blank" class="text-sm text-[--ui-text-muted]">
|
||||
Purchase <span class="text-[--ui-text-highlighted]">Nuxt UI Pro</span>
|
||||
<NuxtLink v-if="route.path.startsWith('/pro')" to="https://ui.nuxt.com/pro/purchase" target="_blank" class="text-sm text-[var(--ui-text-muted)]">
|
||||
Purchase <span class="text-[var(--ui-text-highlighted)]">Nuxt UI Pro</span>
|
||||
</NuxtLink>
|
||||
<NuxtLink v-else to="https://github.com/nuxt/ui" target="_blank" class="text-sm text-[--ui-text-muted]">
|
||||
Published under <span class="text-[--ui-text-highlighted]">MIT License</span>
|
||||
<NuxtLink v-else to="https://github.com/nuxt/ui" target="_blank" class="text-sm text-[var(--ui-text-muted)]">
|
||||
Published under <span class="text-[var(--ui-text-highlighted)]">MIT License</span>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -22,10 +22,10 @@ defineShortcuts({
|
||||
<template>
|
||||
<UHeader :ui="{ left: 'min-w-0' }">
|
||||
<template #left>
|
||||
<NuxtLink to="/" class="flex items-end gap-2 font-bold text-xl text-[--ui-text-highlighted] min-w-0" aria-label="Nuxt UI">
|
||||
<NuxtLink to="/" class="flex items-end gap-2 font-bold text-xl text-[var(--ui-text-highlighted)] min-w-0 focus-visible:outline-[var(--ui-primary)]" aria-label="Nuxt UI">
|
||||
<Logo class="w-auto h-6 shrink-0" />
|
||||
|
||||
<UBadge :label="`v${config.version}`" variant="subtle" size="sm" class="-mb-[2px] rounded font-semibold inline-block truncate" />
|
||||
<UBadge :label="`v${config.version}`" variant="subtle" size="sm" class="-mb-[2px] rounded-[var(--ui-radius)] font-semibold inline-block truncate" />
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -32,6 +32,10 @@ const props = defineProps<{
|
||||
* @defaultValue false
|
||||
*/
|
||||
collapse?: boolean
|
||||
/**
|
||||
* A list of line numbers to highlight in the code block
|
||||
*/
|
||||
highlights?: number[]
|
||||
}>()
|
||||
|
||||
const route = useRoute()
|
||||
@@ -105,7 +109,7 @@ const code = computed(() => {
|
||||
`
|
||||
}
|
||||
|
||||
code += `\`\`\`vue`
|
||||
code += `\`\`\`vue${props.highlights?.length ? ` {${props.highlights.join('-')}}` : ''}`
|
||||
|
||||
if (props.external?.length) {
|
||||
code += `
|
||||
@@ -201,7 +205,8 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
|
||||
formatted = await $prettier.format(code.value, {
|
||||
trailingComma: 'none',
|
||||
semi: false,
|
||||
singleQuote: true
|
||||
singleQuote: true,
|
||||
printWidth: 100
|
||||
})
|
||||
} catch {
|
||||
formatted = code.value
|
||||
@@ -214,15 +219,15 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
|
||||
<template>
|
||||
<div class="my-5">
|
||||
<div>
|
||||
<div v-if="options.length" class="flex items-center gap-2.5 border border-[--ui-color-neutral-200] dark:border-[--ui-color-neutral-700] border-b-0 relative rounded-t-[calc(var(--ui-radius)*1.5)] px-4 py-2.5 overflow-x-auto">
|
||||
<div v-if="options.length" class="flex items-center gap-2.5 border border-[var(--ui-color-neutral-200)] dark:border-[var(--ui-color-neutral-700)] border-b-0 relative rounded-t-[calc(var(--ui-radius)*1.5)] px-4 py-2.5 overflow-x-auto">
|
||||
<template v-for="option in options" :key="option.name">
|
||||
<UFormField
|
||||
:label="option.label"
|
||||
size="sm"
|
||||
class="inline-flex ring ring-[--ui-border-accented] rounded"
|
||||
class="inline-flex ring ring-[var(--ui-border-accented)] rounded-[var(--ui-radius)]"
|
||||
:ui="{
|
||||
wrapper: 'bg-[--ui-bg-elevated]/50 rounded-l flex border-r border-[--ui-border-accented]',
|
||||
label: 'text-[--ui-text-muted] px-2 py-1.5',
|
||||
wrapper: 'bg-[var(--ui-bg-elevated)]/50 rounded-l-[var(--ui-radius)] flex border-r border-[var(--ui-border-accented)]',
|
||||
label: 'text-[var(--ui-text-muted)] px-2 py-1.5',
|
||||
container: 'mt-0'
|
||||
}"
|
||||
>
|
||||
@@ -233,7 +238,7 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
|
||||
value-key="value"
|
||||
color="neutral"
|
||||
variant="soft"
|
||||
class="rounded rounded-l-none min-w-12"
|
||||
class="rounded-[var(--ui-radius)] rounded-l-none min-w-12"
|
||||
:search-input="false"
|
||||
:class="[option.name.toLowerCase().endsWith('color') && 'pl-6']"
|
||||
:ui="{ itemLeadingChip: 'size-2' }"
|
||||
@@ -256,14 +261,14 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
|
||||
:model-value="getComponentProp(option.name)"
|
||||
color="neutral"
|
||||
variant="soft"
|
||||
:ui="{ base: 'rounded rounded-l-none min-w-12' }"
|
||||
:ui="{ base: 'rounded-[var(--ui-radius)] rounded-l-none min-w-12' }"
|
||||
@update:model-value="setComponentProp(option.name, $event)"
|
||||
/>
|
||||
</UFormField>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center border border-b-0 border-[--ui-color-neutral-200] dark:border-[--ui-color-neutral-700] relative p-4 z-[1]" :class="[!options.length && 'rounded-t-[calc(var(--ui-radius)*1.5)]', props.class]">
|
||||
<div class="flex justify-center border border-b-0 border-[var(--ui-color-neutral-200)] dark:border-[var(--ui-color-neutral-700)] relative p-4 z-[1]" :class="[!options.length && 'rounded-t-[calc(var(--ui-radius)*1.5)]', props.class]">
|
||||
<component :is="name" v-bind="{ ...componentProps, ...componentEvents }">
|
||||
<template v-for="slot in Object.keys(slots || {})" :key="slot" #[slot]>
|
||||
<ContentSlot :name="slot" unwrap="p">
|
||||
|
||||
@@ -22,13 +22,11 @@ const props = withDefaults(defineProps<{
|
||||
* @defaultValue true
|
||||
*/
|
||||
preview?: boolean
|
||||
|
||||
/**
|
||||
* Whether to show the source code
|
||||
* @defaultValue true
|
||||
*/
|
||||
source?: boolean
|
||||
|
||||
/**
|
||||
* A list of variable props to link to the component.
|
||||
*/
|
||||
@@ -40,6 +38,10 @@ const props = withDefaults(defineProps<{
|
||||
default: any
|
||||
multiple?: boolean
|
||||
}>
|
||||
/**
|
||||
* A list of line numbers to highlight in the code block
|
||||
*/
|
||||
highlights?: number[]
|
||||
}>(), {
|
||||
preview: true,
|
||||
source: true
|
||||
@@ -65,7 +67,7 @@ const code = computed(() => {
|
||||
`
|
||||
}
|
||||
|
||||
code += `\`\`\`vue${props.preview ? '' : ` [${data.pascalName}.vue]`}
|
||||
code += `\`\`\`vue ${props.preview ? '' : ` [${data.pascalName}.vue]`}${props.highlights?.length ? `{${props.highlights.join('-')}}` : ''}
|
||||
${data?.code ?? ''}
|
||||
\`\`\``
|
||||
|
||||
@@ -87,7 +89,8 @@ const { data: ast } = await useAsyncData(`component-example-${camelName}`, async
|
||||
formatted = await $prettier.format(code.value, {
|
||||
trailingComma: 'none',
|
||||
semi: false,
|
||||
singleQuote: true
|
||||
singleQuote: true,
|
||||
printWidth: 100
|
||||
})
|
||||
} catch {
|
||||
formatted = code.value
|
||||
@@ -113,9 +116,9 @@ const optionsValues = ref(props.options?.reduce((acc, option) => {
|
||||
|
||||
<template>
|
||||
<div class="my-5">
|
||||
<div v-if="preview">
|
||||
<div class="border border-[--ui-color-neutral-200] dark:border-[--ui-color-neutral-700] relative z-[1]" :class="[{ 'border-b-0 rounded-t-[calc(var(--ui-radius)*1.5)]': props.source, 'rounded-[calc(var(--ui-radius)*1.5)]': !props.source }]">
|
||||
<div v-if="props.options?.length || !!slots.options" class="flex gap-4 p-4 border-b border-[--ui-color-neutral-200] dark:border-[--ui-color-neutral-700]">
|
||||
<template v-if="preview">
|
||||
<div class="border border-[var(--ui-color-neutral-200)] dark:border-[var(--ui-color-neutral-700)] relative z-[1]" :class="[{ 'border-b-0 rounded-t-[calc(var(--ui-radius)*1.5)]': props.source, 'rounded-[calc(var(--ui-radius)*1.5)]': !props.source }]">
|
||||
<div v-if="props.options?.length || !!slots.options" class="flex gap-4 p-4 border-b border-[var(--ui-color-neutral-200)] dark:border-[var(--ui-color-neutral-700)]">
|
||||
<slot name="options" />
|
||||
|
||||
<UFormField
|
||||
@@ -124,10 +127,10 @@ const optionsValues = ref(props.options?.reduce((acc, option) => {
|
||||
:label="option.label"
|
||||
:name="option.name"
|
||||
size="sm"
|
||||
class="inline-flex ring ring-[--ui-border-accented] rounded"
|
||||
class="inline-flex ring ring-[var(--ui-border-accented)] rounded-[var(--ui-radius)]"
|
||||
:ui="{
|
||||
wrapper: 'bg-[--ui-bg-elevated]/50 rounded-l flex border-r border-[--ui-border-accented]',
|
||||
label: 'text-[--ui-text-muted] px-2 py-1.5',
|
||||
wrapper: 'bg-[var(--ui-bg-elevated)]/50 rounded-l-[var(--ui-radius)] flex border-r border-[var(--ui-border-accented)]',
|
||||
label: 'text-[var(--ui-text-muted)] px-2 py-1.5',
|
||||
container: 'mt-0'
|
||||
}"
|
||||
>
|
||||
@@ -139,7 +142,7 @@ const optionsValues = ref(props.options?.reduce((acc, option) => {
|
||||
:value-key="option.name.toLowerCase().endsWith('color') ? 'value' : undefined"
|
||||
color="neutral"
|
||||
variant="soft"
|
||||
class="rounded rounded-l-none min-w-12"
|
||||
class="rounded-[var(--ui-radius)] rounded-l-none min-w-12"
|
||||
:multiple="option.multiple"
|
||||
:class="[option.name.toLowerCase().endsWith('color') && 'pl-6']"
|
||||
:ui="{ itemLeadingChip: 'size-2' }"
|
||||
@@ -160,7 +163,7 @@ const optionsValues = ref(props.options?.reduce((acc, option) => {
|
||||
:model-value="get(optionsValues, option.name)"
|
||||
color="neutral"
|
||||
variant="soft"
|
||||
:ui="{ base: 'rounded rounded-l-none min-w-12' }"
|
||||
:ui="{ base: 'rounded-[var(--ui-radius)] rounded-l-none min-w-12' }"
|
||||
@update:model-value="set(optionsValues, option.name, $event)"
|
||||
/>
|
||||
</UFormField>
|
||||
@@ -170,7 +173,7 @@ const optionsValues = ref(props.options?.reduce((acc, option) => {
|
||||
<component :is="camelName" v-bind="{ ...componentProps, ...optionsValues }" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<MDCRenderer v-if="ast && props.source" :body="ast.body" :data="ast.data" class="[&_pre]:!rounded-t-none [&_div.my-5]:!mt-0" />
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,29 @@ import { upperFirst, camelCase } from 'scule'
|
||||
import type { ComponentMeta } from 'vue-component-meta'
|
||||
import * as theme from '#build/ui'
|
||||
|
||||
const props = defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
ignore?: string[]
|
||||
}>()
|
||||
}>(), {
|
||||
ignore: () => [
|
||||
'activeClass',
|
||||
'inactiveClass',
|
||||
'exactActiveClass',
|
||||
'ariaCurrentValue',
|
||||
'href',
|
||||
'rel',
|
||||
'noRel',
|
||||
'prefetch',
|
||||
'prefetchOn',
|
||||
'noPrefetch',
|
||||
'prefetchedClass',
|
||||
'replace',
|
||||
'exact',
|
||||
'exactQuery',
|
||||
'exactHash',
|
||||
'external',
|
||||
'onClick'
|
||||
]
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
@@ -77,8 +97,9 @@ const metaProps: ComputedRef<ComponentMeta['props']> = computed(() => {
|
||||
<ProseTd>
|
||||
<HighlightInlineType v-if="prop.type" :type="prop.type" />
|
||||
|
||||
<MDC v-if="prop.description" :value="prop.description" class="text-[--ui-text-toned] mt-1" />
|
||||
<MDC v-if="prop.description" :value="prop.description" class="text-[var(--ui-text-toned)] mt-1" />
|
||||
|
||||
<ComponentPropsLinks v-if="prop.tags?.length" :prop="prop" />
|
||||
<ComponentPropsSchema v-if="prop.schema" :prop="prop" :ignore="ignore" />
|
||||
</ProseTd>
|
||||
</ProseTr>
|
||||
|
||||
17
docs/app/components/content/ComponentPropsLinks.vue
Normal file
17
docs/app/components/content/ComponentPropsLinks.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import type { PropertyMeta } from 'vue-component-meta'
|
||||
|
||||
const props = defineProps<{
|
||||
prop: PropertyMeta
|
||||
}>()
|
||||
|
||||
const links = computed(() => props.prop.tags?.filter((tag: any) => tag.name === 'link'))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ProseUl v-if="links?.length">
|
||||
<ProseLi v-for="link in links" :key="link.name">
|
||||
<MDC :value="link.text ?? ''" class="my-1" />
|
||||
</ProseLi>
|
||||
</ProseUl>
|
||||
</template>
|
||||
@@ -40,7 +40,7 @@ const schemaProps = computed(() => {
|
||||
<ProseLi v-for="schemaProp in schemaProps" :key="schemaProp.name">
|
||||
<HighlightInlineType :type="`${schemaProp.name}${schemaProp.required === false ? '?' : ''}: ${schemaProp.type}`" />
|
||||
|
||||
<MDC v-if="schemaProp.description" :value="schemaProp.description" class="text-[--ui-text-muted] my-1" />
|
||||
<MDC v-if="schemaProp.description" :value="schemaProp.description" class="text-[var(--ui-text-muted)] my-1" />
|
||||
</ProseLi>
|
||||
</ProseUl>
|
||||
</Collapsible>
|
||||
|
||||
@@ -31,7 +31,7 @@ const meta = await fetchComponentMeta(name as any)
|
||||
<ProseTd>
|
||||
<HighlightInlineType v-if="slot.type" :type="slot.type" />
|
||||
|
||||
<MDC v-if="slot.description" :value="slot.description" class="text-[--ui-text-toned] mt-1" />
|
||||
<MDC v-if="slot.description" :value="slot.description" class="text-[var(--ui-text-toned)] mt-1" />
|
||||
</ProseTd>
|
||||
</ProseTr>
|
||||
</ProseTbody>
|
||||
|
||||
@@ -28,6 +28,14 @@ function stripCompoundVariants(component?: any) {
|
||||
}
|
||||
}
|
||||
|
||||
if (compoundVariant.loadingColor) {
|
||||
if (!['primary', 'neutral'].includes(compoundVariant.loadingColor)) {
|
||||
strippedCompoundVariants.value = true
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="relative overflow-hidden rounded-[--ui-radius] border border-dashed border-[--ui-border-accented] opacity-75 px-4 flex items-center justify-center">
|
||||
<svg class="absolute inset-0 h-full w-full stroke-[--ui-border-inverted]/10" fill="none">
|
||||
<div class="relative overflow-hidden rounded-[var(--ui-radius)] border border-dashed border-[var(--ui-border-accented)] opacity-75 px-4 flex items-center justify-center">
|
||||
<svg class="absolute inset-0 h-full w-full stroke-[var(--ui-border-inverted)]/10" fill="none">
|
||||
<defs>
|
||||
<pattern
|
||||
id="pattern-5c1e4f0e-62d5-498b-8ff0-cf77bb448c8e"
|
||||
|
||||
@@ -18,7 +18,7 @@ const items = [
|
||||
<template>
|
||||
<UAccordion :items="items">
|
||||
<template #content="{ item }">
|
||||
<p class="pb-3.5 text-sm text-[--ui-text-muted]">
|
||||
<p class="pb-3.5 text-sm text-[var(--ui-text-muted)]">
|
||||
This is the {{ item.label }} panel.
|
||||
</p>
|
||||
</template>
|
||||
|
||||
@@ -22,7 +22,7 @@ const items = [
|
||||
<template>
|
||||
<UAccordion :items="items">
|
||||
<template #colors="{ item }">
|
||||
<p class="text-sm pb-3.5 text-[--ui-primary]">
|
||||
<p class="text-sm pb-3.5 text-[var(--ui-primary)]">
|
||||
{{ item.content }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
@@ -2,21 +2,21 @@
|
||||
<UAvatarGroup>
|
||||
<UChip inset color="success">
|
||||
<UAvatar
|
||||
src="https://avatars.githubusercontent.com/u/739984?v=4"
|
||||
src="https://github.com/benjamincanac.png"
|
||||
alt="Benjamin Canac"
|
||||
/>
|
||||
</UChip>
|
||||
|
||||
<UChip inset color="warning">
|
||||
<UAvatar
|
||||
src="https://avatars.githubusercontent.com/u/25613751?v=4"
|
||||
src="https://github.com/romhml.png"
|
||||
alt="Romain Hamel"
|
||||
/>
|
||||
</UChip>
|
||||
|
||||
<UChip inset color="error">
|
||||
<UAvatar
|
||||
src="https://avatars.githubusercontent.com/u/19751938?v=4"
|
||||
src="https://github.com/noook.png"
|
||||
alt="Neil Richter"
|
||||
/>
|
||||
</UChip>
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
<ULink
|
||||
to="https://github.com/benjamincanac"
|
||||
target="_blank"
|
||||
class="hover:ring-[--ui-primary] transition"
|
||||
class="hover:ring-[var(--ui-primary)] transition"
|
||||
raw
|
||||
>
|
||||
<UAvatar
|
||||
src="https://avatars.githubusercontent.com/u/739984?v=4"
|
||||
src="https://github.com/benjamincanac.png"
|
||||
alt="Benjamin Canac"
|
||||
/>
|
||||
</ULink>
|
||||
@@ -15,11 +15,11 @@
|
||||
<ULink
|
||||
to="https://github.com/romhml"
|
||||
target="_blank"
|
||||
class="hover:ring-[--ui-primary] transition"
|
||||
class="hover:ring-[var(--ui-primary)] transition"
|
||||
raw
|
||||
>
|
||||
<UAvatar
|
||||
src="https://avatars.githubusercontent.com/u/25613751?v=4"
|
||||
src="https://github.com/romhml.png"
|
||||
alt="Romain Hamel"
|
||||
/>
|
||||
</ULink>
|
||||
@@ -27,11 +27,11 @@
|
||||
<ULink
|
||||
to="https://github.com/noook"
|
||||
target="_blank"
|
||||
class="hover:ring-[--ui-primary] transition"
|
||||
class="hover:ring-[var(--ui-primary)] transition"
|
||||
raw
|
||||
>
|
||||
<UAvatar
|
||||
src="https://avatars.githubusercontent.com/u/19751938?v=4"
|
||||
src="https://github.com/noook.png"
|
||||
alt="Neil Richter"
|
||||
/>
|
||||
</ULink>
|
||||
|
||||
@@ -2,21 +2,21 @@
|
||||
<UAvatarGroup>
|
||||
<UTooltip text="benjamincanac">
|
||||
<UAvatar
|
||||
src="https://avatars.githubusercontent.com/u/739984?v=4"
|
||||
src="https://github.com/benjamincanac.png"
|
||||
alt="Benjamin Canac"
|
||||
/>
|
||||
</UTooltip>
|
||||
|
||||
<UTooltip text="romhml">
|
||||
<UAvatar
|
||||
src="https://avatars.githubusercontent.com/u/25613751?v=4"
|
||||
src="https://github.com/romhml.png"
|
||||
alt="Romain Hamel"
|
||||
/>
|
||||
</UTooltip>
|
||||
|
||||
<UTooltip text="noook">
|
||||
<UAvatar
|
||||
src="https://avatars.githubusercontent.com/u/19751938?v=4"
|
||||
src="https://github.com/noook.png"
|
||||
alt="Neil Richter"
|
||||
/>
|
||||
</UTooltip>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<UChip inset>
|
||||
<UAvatar
|
||||
src="https://avatars.githubusercontent.com/u/739984?v=4"
|
||||
src="https://github.com/benjamincanac.png"
|
||||
alt="Benjamin Canac"
|
||||
/>
|
||||
</UChip>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<UTooltip text="Benjamin Canac">
|
||||
<UAvatar
|
||||
src="https://avatars.githubusercontent.com/u/739984?v=4"
|
||||
src="https://github.com/benjamincanac.png"
|
||||
alt="Benjamin Canac"
|
||||
/>
|
||||
</UTooltip>
|
||||
|
||||
@@ -14,7 +14,7 @@ const items = [{
|
||||
<template>
|
||||
<UBreadcrumb :items="items">
|
||||
<template #separator>
|
||||
<span class="mx-2 text-[--ui-text-muted]">/</span>
|
||||
<span class="mx-2 text-[var(--ui-text-muted)]">/</span>
|
||||
</template>
|
||||
</UBreadcrumb>
|
||||
</template>
|
||||
|
||||
@@ -18,6 +18,6 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<UChip :color="color" :show="show" inset>
|
||||
<UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" />
|
||||
<UAvatar src="https://github.com/benjamincanac.png" />
|
||||
</UChip>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<UCollapsible class="w-48">
|
||||
<UCollapsible class="flex flex-col gap-2 w-48">
|
||||
<UButton
|
||||
class="group"
|
||||
label="Open"
|
||||
|
||||
@@ -7,7 +7,7 @@ defineShortcuts({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCollapsible v-model:open="open" class="w-48">
|
||||
<UCollapsible v-model:open="open" class="flex flex-col gap-2 w-48">
|
||||
<UButton
|
||||
label="Open"
|
||||
color="neutral"
|
||||
|
||||
@@ -1,40 +1,68 @@
|
||||
<script setup lang="ts">
|
||||
const groups = [{
|
||||
id: 'settings',
|
||||
items: [{
|
||||
label: 'Profile',
|
||||
icon: 'i-heroicons-user',
|
||||
kbds: ['meta', 'P']
|
||||
}, {
|
||||
label: 'Billing',
|
||||
icon: 'i-heroicons-credit-card',
|
||||
kbds: ['meta', 'B'],
|
||||
slot: 'billing'
|
||||
}, {
|
||||
label: 'Notifications',
|
||||
icon: 'i-heroicons-bell'
|
||||
}, {
|
||||
label: 'Security',
|
||||
icon: 'i-heroicons-lock-closed'
|
||||
}]
|
||||
items: [
|
||||
{
|
||||
label: 'Profile',
|
||||
icon: 'i-heroicons-user',
|
||||
kbds: ['meta', 'P']
|
||||
},
|
||||
{
|
||||
label: 'Billing',
|
||||
icon: 'i-heroicons-credit-card',
|
||||
kbds: ['meta', 'B'],
|
||||
slot: 'billing'
|
||||
},
|
||||
{
|
||||
label: 'Notifications',
|
||||
icon: 'i-heroicons-bell'
|
||||
},
|
||||
{
|
||||
label: 'Security',
|
||||
icon: 'i-heroicons-lock-closed'
|
||||
}
|
||||
]
|
||||
}, {
|
||||
id: 'users',
|
||||
label: 'Users',
|
||||
slot: 'users',
|
||||
items: [
|
||||
{ id: 1, label: 'Durward Reynolds' },
|
||||
{ id: 2, label: 'Kenton Towne' },
|
||||
{ id: 3, label: 'Therese Wunsch' },
|
||||
{ id: 4, label: 'Benedict Kessler' },
|
||||
{ id: 5, label: 'Katelyn Rohan' }
|
||||
{
|
||||
label: 'Benjamin Canac',
|
||||
suffix: 'benjamincanac'
|
||||
},
|
||||
{
|
||||
label: 'Sylvain Marroufin',
|
||||
suffix: 'smarroufin'
|
||||
},
|
||||
{
|
||||
label: 'Sébastien Chopin',
|
||||
suffix: 'atinux'
|
||||
},
|
||||
{
|
||||
label: 'Romain Hamel',
|
||||
suffix: 'romhml'
|
||||
},
|
||||
{
|
||||
label: 'Haytham A. Salama',
|
||||
suffix: 'Haythamasalama'
|
||||
},
|
||||
{
|
||||
label: 'Daniel Roe',
|
||||
suffix: 'danielroe'
|
||||
},
|
||||
{
|
||||
label: 'Neil Richter',
|
||||
suffix: 'noook'
|
||||
}
|
||||
]
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCommandPalette :groups="groups" class="flex-1 h-80">
|
||||
<template #users-leading="{ index }">
|
||||
<UAvatar :src="`https://i.pravatar.cc/120?img=${index}`" size="2xs" />
|
||||
<template #users-leading="{ item }">
|
||||
<UAvatar :src="`https://github.com/${item.suffix}.png`" size="2xs" />
|
||||
</template>
|
||||
|
||||
<template #billing-label="{ item }">
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
const users = [
|
||||
{ id: 1, label: 'Durward Reynolds' },
|
||||
{ id: 2, label: 'Kenton Towne' },
|
||||
{ id: 3, label: 'Therese Wunsch' },
|
||||
{ id: 4, label: 'Benedict Kessler' },
|
||||
{ id: 5, label: 'Katelyn Rohan' }
|
||||
]
|
||||
|
||||
const selected = ref(users[0])
|
||||
|
||||
function onSelect(item: any) {
|
||||
console.log(item)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCommandPalette
|
||||
v-model="selected"
|
||||
:groups="[{ id: 'users', items: users }]"
|
||||
class="flex-1"
|
||||
@update:model-value="onSelect"
|
||||
/>
|
||||
</template>
|
||||
@@ -1,25 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
const users = [
|
||||
{ id: 1, label: 'Durward Reynolds' },
|
||||
{ id: 2, label: 'Kenton Towne' },
|
||||
{ id: 3, label: 'Therese Wunsch' },
|
||||
{ id: 4, label: 'Benedict Kessler' },
|
||||
{ id: 5, label: 'Katelyn Rohan' }
|
||||
]
|
||||
|
||||
const selected = ref([users[0], users[1]])
|
||||
|
||||
function onSelect(items: any) {
|
||||
console.log(items)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCommandPalette
|
||||
v-model="selected"
|
||||
multiple
|
||||
:groups="[{ id: 'users', items: users }]"
|
||||
class="flex-1"
|
||||
@update:model-value="onSelect"
|
||||
/>
|
||||
</template>
|
||||
@@ -2,11 +2,55 @@
|
||||
const open = ref(false)
|
||||
|
||||
const users = [
|
||||
{ id: 1, label: 'Durward Reynolds' },
|
||||
{ id: 2, label: 'Kenton Towne' },
|
||||
{ id: 3, label: 'Therese Wunsch' },
|
||||
{ id: 4, label: 'Benedict Kessler' },
|
||||
{ id: 5, label: 'Katelyn Rohan' }
|
||||
{
|
||||
label: 'Benjamin Canac',
|
||||
suffix: 'benjamincanac',
|
||||
avatar: {
|
||||
src: 'https://github.com/benjamincanac.png'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Sylvain Marroufin',
|
||||
suffix: 'smarroufin',
|
||||
avatar: {
|
||||
src: 'https://github.com/smarroufin.png'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Sébastien Chopin',
|
||||
suffix: 'atinux',
|
||||
avatar: {
|
||||
src: 'https://github.com/atinux.png'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Romain Hamel',
|
||||
suffix: 'romhml',
|
||||
avatar: {
|
||||
src: 'https://github.com/romhml.png'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Haytham A. Salama',
|
||||
suffix: 'Haythamasalama',
|
||||
avatar: {
|
||||
src: 'https://github.com/Haythamasalama.png'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Daniel Roe',
|
||||
suffix: 'danielroe',
|
||||
avatar: {
|
||||
src: 'https://github.com/danielroe.png'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Neil Richter',
|
||||
suffix: 'noook',
|
||||
avatar: {
|
||||
src: 'https://github.com/noook.png'
|
||||
}
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
@@ -20,11 +64,7 @@ const users = [
|
||||
/>
|
||||
|
||||
<template #content>
|
||||
<UCommandPalette
|
||||
close
|
||||
:groups="[{ id: 'users', items: users }]"
|
||||
@update:open="open = $event"
|
||||
/>
|
||||
<UCommandPalette close :groups="[{ id: 'users', items: users }]" @update:open="open = $event" />
|
||||
</template>
|
||||
</UModal>
|
||||
</template>
|
||||
|
||||
@@ -1,29 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
const items = [{
|
||||
id: '/',
|
||||
label: 'Introduction',
|
||||
level: 1
|
||||
}, {
|
||||
id: '/getting-started#whats-new-in-v3',
|
||||
label: 'What\'s new in v3?',
|
||||
level: 2
|
||||
}, {
|
||||
id: '/getting-started#radix-vue-3',
|
||||
label: 'Radix Vue',
|
||||
level: 3
|
||||
}, {
|
||||
id: '/getting-started#tailwind-css-v4',
|
||||
label: 'Tailwind CSS v4',
|
||||
level: 3
|
||||
}, {
|
||||
id: '/getting-started#tailwind-variants',
|
||||
label: 'Tailwind Variants',
|
||||
level: 3
|
||||
}, {
|
||||
id: '/getting-started/installation',
|
||||
label: 'Installation',
|
||||
level: 1
|
||||
}]
|
||||
const items = [
|
||||
{
|
||||
id: '/',
|
||||
label: 'Introduction',
|
||||
level: 1
|
||||
},
|
||||
{
|
||||
id: '/getting-started#whats-new-in-v3',
|
||||
label: 'What\'s new in v3?',
|
||||
level: 2
|
||||
},
|
||||
{
|
||||
id: '/getting-started#radix-vue-3',
|
||||
label: 'Radix Vue',
|
||||
level: 3
|
||||
},
|
||||
{
|
||||
id: '/getting-started#tailwind-css-v4',
|
||||
label: 'Tailwind CSS v4',
|
||||
level: 3
|
||||
},
|
||||
{
|
||||
id: '/getting-started#tailwind-variants',
|
||||
label: 'Tailwind Variants',
|
||||
level: 3
|
||||
},
|
||||
{
|
||||
id: '/getting-started/installation',
|
||||
label: 'Installation',
|
||||
level: 1
|
||||
}
|
||||
]
|
||||
|
||||
function postFilter(searchTerm: string, items: any[]) {
|
||||
// Filter only first level items if no searchTerm
|
||||
|
||||
@@ -1,13 +1,82 @@
|
||||
<script setup lang="ts">
|
||||
const users = [
|
||||
{ id: 1, label: 'Durward Reynolds' },
|
||||
{ id: 2, label: 'Kenton Towne' },
|
||||
{ id: 3, label: 'Therese Wunsch' },
|
||||
{ id: 4, label: 'Benedict Kessler' },
|
||||
{ id: 5, label: 'Katelyn Rohan' }
|
||||
{
|
||||
label: 'Benjamin Canac',
|
||||
suffix: 'benjamincanac',
|
||||
to: 'https://github.com/benjamincanac',
|
||||
target: '_blank',
|
||||
avatar: {
|
||||
src: 'https://github.com/benjamincanac.png',
|
||||
alt: 'benjamincanac'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Sylvain Marroufin',
|
||||
suffix: 'smarroufin',
|
||||
to: 'https://github.com/smarroufin',
|
||||
target: '_blank',
|
||||
avatar: {
|
||||
src: 'https://github.com/smarroufin.png',
|
||||
alt: 'smarroufin'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Sébastien Chopin',
|
||||
suffix: 'atinux',
|
||||
to: 'https://github.com/atinux',
|
||||
target: '_blank',
|
||||
avatar: {
|
||||
src: 'https://github.com/atinux.png',
|
||||
alt: 'atinux'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Romain Hamel',
|
||||
suffix: 'romhml',
|
||||
to: 'https://github.com/romhml',
|
||||
target: '_blank',
|
||||
avatar: {
|
||||
src: 'https://github.com/romhml.png',
|
||||
alt: 'romhml'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Haytham A. Salama',
|
||||
suffix: 'Haythamasalama',
|
||||
to: 'https://github.com/Haythamasalama',
|
||||
target: '_blank',
|
||||
avatar: {
|
||||
src: 'https://github.com/Haythamasalama.png',
|
||||
alt: 'Haythamasalama'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Daniel Roe',
|
||||
suffix: 'danielroe',
|
||||
to: 'https://github.com/danielroe',
|
||||
target: '_blank',
|
||||
avatar: {
|
||||
src: 'https://github.com/danielroe.png',
|
||||
alt: 'danielroe'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Neil Richter',
|
||||
suffix: 'noook',
|
||||
to: 'https://github.com/noook',
|
||||
target: '_blank',
|
||||
avatar: {
|
||||
src: 'https://github.com/noook.png',
|
||||
alt: 'noook'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const searchTerm = ref('Th')
|
||||
const searchTerm = ref('')
|
||||
|
||||
function onSelect() {
|
||||
searchTerm.value = ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -15,5 +84,6 @@ const searchTerm = ref('Th')
|
||||
v-model:search-term="searchTerm"
|
||||
:groups="[{ id: 'users', items: users }]"
|
||||
class="flex-1"
|
||||
@update:model-value="onSelect"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
<script setup lang="ts">
|
||||
const router = useRouter()
|
||||
|
||||
const groups = ref([
|
||||
{
|
||||
id: 'users',
|
||||
label: 'Users',
|
||||
items: [
|
||||
{
|
||||
label: 'Benjamin Canac',
|
||||
suffix: 'benjamincanac',
|
||||
to: 'https://github.com/benjamincanac',
|
||||
target: '_blank',
|
||||
avatar: {
|
||||
src: 'https://github.com/benjamincanac.png',
|
||||
alt: 'benjamincanac'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Sylvain Marroufin',
|
||||
suffix: 'smarroufin',
|
||||
to: 'https://github.com/smarroufin',
|
||||
target: '_blank',
|
||||
avatar: {
|
||||
src: 'https://github.com/smarroufin.png',
|
||||
alt: 'smarroufin'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Sébastien Chopin',
|
||||
suffix: 'atinux',
|
||||
to: 'https://github.com/atinux',
|
||||
target: '_blank',
|
||||
avatar: {
|
||||
src: 'https://github.com/atinux.png',
|
||||
alt: 'atinux'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Romain Hamel',
|
||||
suffix: 'romhml',
|
||||
to: 'https://github.com/romhml',
|
||||
target: '_blank',
|
||||
avatar: {
|
||||
src: 'https://github.com/romhml.png',
|
||||
alt: 'romhml'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Haytham A. Salama',
|
||||
suffix: 'Haythamasalama',
|
||||
to: 'https://github.com/Haythamasalama',
|
||||
target: '_blank',
|
||||
avatar: {
|
||||
src: 'https://github.com/Haythamasalama.png',
|
||||
alt: 'Haythamasalama'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Daniel Roe',
|
||||
suffix: 'danielroe',
|
||||
to: 'https://github.com/danielroe',
|
||||
target: '_blank',
|
||||
avatar: {
|
||||
src: 'https://github.com/danielroe.png',
|
||||
alt: 'danielroe'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Neil Richter',
|
||||
suffix: 'noook',
|
||||
to: 'https://github.com/noook',
|
||||
target: '_blank',
|
||||
avatar: {
|
||||
src: 'https://github.com/noook.png',
|
||||
alt: 'noook'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
items: [
|
||||
{
|
||||
label: 'Add new file',
|
||||
suffix: 'Create a new file in the current directory or workspace.',
|
||||
icon: 'i-heroicons-document-plus',
|
||||
kbds: [
|
||||
'meta',
|
||||
'N'
|
||||
],
|
||||
onSelect() {
|
||||
console.log('Add new file')
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Add new folder',
|
||||
suffix: 'Create a new folder in the current directory or workspace.',
|
||||
icon: 'i-heroicons-folder-plus',
|
||||
kbds: [
|
||||
'meta',
|
||||
'F'
|
||||
],
|
||||
onSelect() {
|
||||
console.log('Add new folder')
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Add hashtag',
|
||||
suffix: 'Add a hashtag to the current item.',
|
||||
icon: 'i-heroicons-hashtag',
|
||||
kbds: [
|
||||
'meta',
|
||||
'H'
|
||||
],
|
||||
onSelect() {
|
||||
console.log('Add hashtag')
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Add label',
|
||||
suffix: 'Add a label to the current item.',
|
||||
icon: 'i-heroicons-tag',
|
||||
kbds: [
|
||||
'meta',
|
||||
'L'
|
||||
],
|
||||
onSelect() {
|
||||
console.log('Add label')
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
function onSelect(item: any) {
|
||||
if (item.onSelect) {
|
||||
item.onSelect()
|
||||
} else if (item.to) {
|
||||
if (typeof item.to === 'string' && (item.target === '_blank' || item.to.startsWith('http'))) {
|
||||
window.open(item.to, item.target || '_blank')
|
||||
} else {
|
||||
router.push(item.to)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCommandPalette
|
||||
:groups="groups"
|
||||
class="flex-1 h-80"
|
||||
@update:model-value="onSelect"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
const showSidebar = ref(true)
|
||||
const showToolbar = ref(false)
|
||||
|
||||
const items = computed(() => [{
|
||||
label: 'View',
|
||||
type: 'label' as const
|
||||
}, {
|
||||
type: 'separator' as const
|
||||
}, {
|
||||
label: 'Show Sidebar',
|
||||
type: 'checkbox' as const,
|
||||
checked: showSidebar.value,
|
||||
onUpdateChecked(checked: boolean) {
|
||||
showSidebar.value = checked
|
||||
},
|
||||
onSelect(e: Event) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}, {
|
||||
label: 'Show Toolbar',
|
||||
type: 'checkbox' as const,
|
||||
checked: showToolbar.value,
|
||||
onUpdateChecked(checked: boolean) {
|
||||
showToolbar.value = checked
|
||||
}
|
||||
}, {
|
||||
label: 'Collapse Pinned Tabs',
|
||||
type: 'checkbox' as const,
|
||||
disabled: true
|
||||
}])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UContextMenu :items="items" class="w-48">
|
||||
<div class="flex items-center justify-center rounded-md border border-dashed border-[var(--ui-border-accented)] text-sm aspect-video w-72">
|
||||
Right click here
|
||||
</div>
|
||||
</UContextMenu>
|
||||
</template>
|
||||
@@ -13,7 +13,7 @@ const items = [{
|
||||
|
||||
<template>
|
||||
<UContextMenu :items="items" class="w-48">
|
||||
<div class="flex items-center justify-center rounded-md border border-dashed border-[--ui-border-accented] text-sm aspect-video w-72">
|
||||
<div class="flex items-center justify-center rounded-md border border-dashed border-[var(--ui-border-accented)] text-sm aspect-video w-72">
|
||||
Right click here
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@ const items = [{
|
||||
</template>
|
||||
|
||||
<template #refresh-trailing>
|
||||
<UIcon v-if="loading" name="i-heroicons-arrow-path-20-solid" class="shrink-0 size-5 text-[--ui-primary] animate-spin" />
|
||||
<UIcon v-if="loading" name="i-heroicons-arrow-path-20-solid" class="shrink-0 size-5 text-[var(--ui-primary)] animate-spin" />
|
||||
</template>
|
||||
</UContextMenu>
|
||||
</template>
|
||||
|
||||
@@ -18,7 +18,7 @@ const groups = computed(() => [{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDrawer>
|
||||
<UDrawer :handle="false">
|
||||
<UButton
|
||||
label="Search users..."
|
||||
color="neutral"
|
||||
@@ -32,7 +32,7 @@ const groups = computed(() => [{
|
||||
:loading="status === 'pending'"
|
||||
:groups="groups"
|
||||
placeholder="Search users..."
|
||||
class="h-96 border-t border-[--ui-border]"
|
||||
class="h-80"
|
||||
/>
|
||||
</template>
|
||||
</UDrawer>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
const showBookmarks = ref(true)
|
||||
const showHistory = ref(false)
|
||||
const showDownloads = ref(false)
|
||||
|
||||
const items = computed(() => [{
|
||||
label: 'Interface',
|
||||
icon: 'i-heroicons-window',
|
||||
type: 'label' as const
|
||||
}, {
|
||||
type: 'separator' as const
|
||||
}, {
|
||||
label: 'Show Bookmarks',
|
||||
icon: 'i-heroicons-bookmark',
|
||||
type: 'checkbox' as const,
|
||||
checked: showBookmarks.value,
|
||||
onUpdateChecked(checked: boolean) {
|
||||
showBookmarks.value = checked
|
||||
},
|
||||
onSelect(e: Event) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}, {
|
||||
label: 'Show History',
|
||||
icon: 'i-heroicons-clock',
|
||||
type: 'checkbox' as const,
|
||||
checked: showHistory.value,
|
||||
onUpdateChecked(checked: boolean) {
|
||||
showHistory.value = checked
|
||||
}
|
||||
}, {
|
||||
label: 'Show Downloads',
|
||||
icon: 'i-heroicons-arrow-down-on-square',
|
||||
type: 'checkbox' as const,
|
||||
checked: showDownloads.value,
|
||||
onUpdateChecked(checked: boolean) {
|
||||
showDownloads.value = checked
|
||||
}
|
||||
}])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdownMenu :items="items" :content="{ align: 'start' }" class="w-48">
|
||||
<UButton label="Open" color="neutral" variant="outline" icon="i-heroicons-bars-3" />
|
||||
</UDropdownMenu>
|
||||
</template>
|
||||
@@ -17,7 +17,7 @@ const items = [{
|
||||
<UButton label="Open" color="neutral" variant="outline" icon="i-heroicons-bars-3" />
|
||||
|
||||
<template #profile-trailing>
|
||||
<UIcon name="i-heroicons-check-badge" class="shrink-0 size-5 text-[--ui-primary]" />
|
||||
<UIcon name="i-heroicons-check-badge" class="shrink-0 size-5 text-[var(--ui-primary)]" />
|
||||
</template>
|
||||
</UDropdownMenu>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { z } from 'zod'
|
||||
import type { FormSubmitEvent, Form } from '@nuxt/ui'
|
||||
import type { FormSubmitEvent } from '@nuxt/ui'
|
||||
|
||||
const schema = z.object({
|
||||
input: z.string().min(10),
|
||||
@@ -35,7 +35,8 @@ const schema = z.object({
|
||||
type Schema = z.output<typeof schema>
|
||||
|
||||
const state = reactive<Partial<Schema>>({})
|
||||
const form = ref<Form<Schema>>()
|
||||
|
||||
const form = useTemplateRef('form')
|
||||
|
||||
const items = [
|
||||
{ label: 'Option 1', value: 'option-1' },
|
||||
|
||||
@@ -31,9 +31,9 @@ function removeItem() {
|
||||
state.items.pop()
|
||||
}
|
||||
}
|
||||
const formItemRef = ref()
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
async function onSubmit(event: FormSubmitEvent<any>) {
|
||||
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
|
||||
console.log(event.data)
|
||||
@@ -42,7 +42,6 @@ async function onSubmit(event: FormSubmitEvent<any>) {
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
ref="formItemRef"
|
||||
:state="state"
|
||||
:schema="schema"
|
||||
class="gap-4 flex flex-col w-60"
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
import { object, string, nonempty, refine, type Infer } from 'superstruct'
|
||||
import type { FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const schema = object({
|
||||
email: nonempty(string()),
|
||||
password: refine(string(), 'Password', (value) => {
|
||||
if (value.length >= 8) return true
|
||||
return 'Must be at least 8 characters'
|
||||
})
|
||||
})
|
||||
|
||||
const state = reactive({
|
||||
email: '',
|
||||
password: ''
|
||||
})
|
||||
|
||||
type Schema = Infer<typeof schema>
|
||||
|
||||
async function onSubmit(event: FormSubmitEvent<Schema>) {
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
|
||||
<UFormField label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormField>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
@@ -17,14 +17,13 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
|
||||
:loading="status === 'pending'"
|
||||
icon="i-heroicons-user"
|
||||
placeholder="Select user"
|
||||
class="w-48"
|
||||
>
|
||||
<template #leading="{ modelValue, ui }">
|
||||
<UAvatar
|
||||
v-if="modelValue"
|
||||
v-bind="modelValue.avatar"
|
||||
:size="ui.itemLeadingAvatarSize()"
|
||||
:class="ui.itemLeadingAvatar()"
|
||||
:size="ui.leadingAvatarSize()"
|
||||
:class="ui.leadingAvatar()"
|
||||
/>
|
||||
</template>
|
||||
</UInputMenu>
|
||||
|
||||
@@ -23,14 +23,13 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
|
||||
:filter="false"
|
||||
icon="i-heroicons-user"
|
||||
placeholder="Select user"
|
||||
class="w-48"
|
||||
>
|
||||
<template #leading="{ modelValue, ui }">
|
||||
<UAvatar
|
||||
v-if="modelValue"
|
||||
v-bind="modelValue.avatar"
|
||||
:size="ui.itemLeadingAvatarSize()"
|
||||
:class="ui.itemLeadingAvatar()"
|
||||
:size="ui.leadingAvatarSize()"
|
||||
:class="ui.leadingAvatar()"
|
||||
/>
|
||||
</template>
|
||||
</UInputMenu>
|
||||
|
||||
@@ -25,15 +25,15 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
|
||||
<UAvatar
|
||||
v-if="modelValue"
|
||||
v-bind="modelValue.avatar"
|
||||
:size="ui.itemLeadingAvatarSize()"
|
||||
:class="ui.itemLeadingAvatar()"
|
||||
:size="ui.leadingAvatarSize()"
|
||||
:class="ui.leadingAvatar()"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #item-label="{ item }">
|
||||
{{ item.label }}
|
||||
|
||||
<span class="text-[--ui-text-muted]">
|
||||
<span class="text-[var(--ui-text-muted)]">
|
||||
{{ item.email }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
|
||||
const selected = ref('Backlog')
|
||||
const value = ref('Backlog')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputMenu
|
||||
v-model="selected"
|
||||
v-model="value"
|
||||
:items="items"
|
||||
:ui="{
|
||||
trailingIcon: 'group-data-[state=open]:rotate-180 transition-transform duration-200'
|
||||
|
||||
@@ -25,18 +25,9 @@ const items = ref([
|
||||
}
|
||||
}
|
||||
])
|
||||
const selected = ref(items.value[0])
|
||||
const value = ref(items.value[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputMenu v-model="selected" :items="items" class="w-40">
|
||||
<template #leading="{ modelValue, ui }">
|
||||
<UAvatar
|
||||
v-if="modelValue"
|
||||
v-bind="modelValue.avatar"
|
||||
:size="ui.itemLeadingAvatarSize()"
|
||||
:class="ui.itemLeadingAvatar()"
|
||||
/>
|
||||
</template>
|
||||
</UInputMenu>
|
||||
<UInputMenu v-model="value" :avatar="value?.avatar" :items="items" />
|
||||
</template>
|
||||
|
||||
@@ -22,11 +22,11 @@ const items = ref([
|
||||
}
|
||||
}
|
||||
])
|
||||
const selected = ref(items.value[0])
|
||||
const value = ref(items.value[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputMenu v-model="selected" :items="items" class="w-40">
|
||||
<UInputMenu v-model="value" :items="items">
|
||||
<template #leading="{ modelValue, ui }">
|
||||
<UChip
|
||||
v-if="modelValue"
|
||||
|
||||
@@ -21,9 +21,9 @@ const items = ref([
|
||||
icon: 'i-heroicons-check-circle'
|
||||
}
|
||||
])
|
||||
const selected = ref(items.value[0])
|
||||
const value = ref(items.value[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputMenu v-model="selected" :icon="selected?.icon" :items="items" class="w-40" />
|
||||
<UInputMenu v-model="value" :icon="value?.icon" :items="items" />
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
const open = ref(false)
|
||||
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
|
||||
const selected = ref('Backlog')
|
||||
const value = ref('Backlog')
|
||||
|
||||
defineShortcuts({
|
||||
o: () => open.value = !open.value
|
||||
@@ -9,5 +9,5 @@ defineShortcuts({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputMenu v-model="selected" v-model:open="open" :items="items" />
|
||||
<UInputMenu v-model="value" v-model:open="open" :items="items" />
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
const open = ref(false)
|
||||
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
|
||||
const selected = ref('Backlog')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputMenu
|
||||
v-model="selected"
|
||||
v-model:open="open"
|
||||
:items="items"
|
||||
@focus="open = true"
|
||||
/>
|
||||
</template>
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
const searchTerm = ref('D')
|
||||
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
|
||||
const selected = ref('Backlog')
|
||||
const value = ref('Backlog')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInputMenu v-model="selected" v-model:search-term="searchTerm" :items="items" />
|
||||
<UInputMenu v-model="value" v-model:search-term="searchTerm" :items="items" />
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
const value = ref('')
|
||||
const domains = ['.com', '.dev', '.org']
|
||||
const domain = ref(domains[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButtonGroup>
|
||||
<UInput
|
||||
v-model="value"
|
||||
placeholder="nuxt"
|
||||
:ui="{
|
||||
base: 'pl-[57px]',
|
||||
leading: 'pointer-events-none'
|
||||
}"
|
||||
>
|
||||
<template #leading>
|
||||
<p class="text-sm text-[var(--ui-text-muted)]">
|
||||
https://
|
||||
</p>
|
||||
</template>
|
||||
</UInput>
|
||||
|
||||
<USelectMenu v-model="domain" :items="domains" />
|
||||
</UButtonGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
const value = ref('')
|
||||
const maxLength = 15
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInput
|
||||
v-model="value"
|
||||
:maxlength="maxLength"
|
||||
aria-describedby="character-count"
|
||||
:ui="{ trailing: 'pointer-events-none' }"
|
||||
>
|
||||
<template #trailing>
|
||||
<div
|
||||
id="character-count"
|
||||
class="text-xs text-[var(--ui-text-muted)] tabular-nums"
|
||||
aria-live="polite"
|
||||
role="status"
|
||||
>
|
||||
{{ value?.length }}/{{ maxLength }}
|
||||
</div>
|
||||
</template>
|
||||
</UInput>
|
||||
</template>
|
||||
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
const value = ref('Click to clear')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInput
|
||||
v-model="value"
|
||||
placeholder="Type something..."
|
||||
:ui="{ trailing: 'pr-0.5' }"
|
||||
>
|
||||
<template v-if="value?.length" #trailing>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="link"
|
||||
size="sm"
|
||||
icon="i-heroicons-x-circle"
|
||||
aria-label="Clear input"
|
||||
@click="value = ''"
|
||||
/>
|
||||
</template>
|
||||
</UInput>
|
||||
</template>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
const value = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInput v-model="value" placeholder="" :ui="{ base: 'peer' }">
|
||||
<label class="pointer-events-none absolute left-0 -top-2.5 text-[var(--ui-text-highlighted)] text-xs font-medium px-1.5 transition-all peer-focus:-top-2.5 peer-focus:text-[var(--ui-text-highlighted)] peer-focus:text-xs peer-focus:font-medium peer-placeholder-shown:text-sm peer-placeholder-shown:text-[var(--ui-text-dimmed)] peer-placeholder-shown:top-1.5 peer-placeholder-shown:font-normal">
|
||||
<span class="inline-flex bg-[var(--ui-bg)] px-1">Email address</span>
|
||||
</label>
|
||||
</UInput>
|
||||
</template>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
const email = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UFormField label="Email" help="We won't share your email." required>
|
||||
<UInput v-model="email" placeholder="Enter your email" icon="i-heroicons-at-symbol" />
|
||||
</UFormField>
|
||||
</template>
|
||||
@@ -0,0 +1,93 @@
|
||||
<script setup lang="ts">
|
||||
const show = ref(false)
|
||||
const password = ref('')
|
||||
|
||||
function checkStrength(str: string) {
|
||||
const requirements = [
|
||||
{ regex: /.{8,}/, text: 'At least 8 characters' },
|
||||
{ regex: /\d/, text: 'At least 1 number' },
|
||||
{ regex: /[a-z]/, text: 'At least 1 lowercase letter' },
|
||||
{ regex: /[A-Z]/, text: 'At least 1 uppercase letter' }
|
||||
]
|
||||
|
||||
return requirements.map(req => ({ met: req.regex.test(str), text: req.text }))
|
||||
}
|
||||
|
||||
const strength = computed(() => checkStrength(password.value))
|
||||
const score = computed(() => strength.value.filter(req => req.met).length)
|
||||
|
||||
const color = computed(() => {
|
||||
if (score.value === 0) return 'neutral'
|
||||
if (score.value <= 1) return 'error'
|
||||
if (score.value <= 2) return 'warning'
|
||||
if (score.value === 3) return 'warning'
|
||||
return 'success'
|
||||
})
|
||||
|
||||
const text = computed(() => {
|
||||
if (score.value === 0) return 'Enter a password'
|
||||
if (score.value <= 2) return 'Weak password'
|
||||
if (score.value === 3) return 'Medium password'
|
||||
return 'Strong password'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-2">
|
||||
<UFormField label="Password">
|
||||
<UInput
|
||||
v-model="password"
|
||||
placeholder="Password"
|
||||
:color="color"
|
||||
:type="show ? 'text' : 'password'"
|
||||
:ui="{ trailing: 'pr-0.5' }"
|
||||
:aria-invalid="score < 4"
|
||||
aria-describedby="password-strength"
|
||||
class="w-full"
|
||||
>
|
||||
<template #trailing>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="link"
|
||||
size="sm"
|
||||
:icon="show ? 'i-heroicons-eye-slash' : 'i-heroicons-eye'"
|
||||
aria-label="show ? 'Hide password' : 'Show password'"
|
||||
:aria-pressed="show"
|
||||
aria-controls="password"
|
||||
@click="show = !show"
|
||||
/>
|
||||
</template>
|
||||
</UInput>
|
||||
</UFormField>
|
||||
|
||||
<UProgress
|
||||
:color="color"
|
||||
:indicator="text"
|
||||
:model-value="score"
|
||||
:max="4"
|
||||
size="sm"
|
||||
/>
|
||||
|
||||
<p id="password-strength" class="text-sm font-medium">
|
||||
{{ text }}. Must contain:
|
||||
</p>
|
||||
|
||||
<ul class="space-y-1" aria-label="Password requirements">
|
||||
<li
|
||||
v-for="(req, index) in strength"
|
||||
:key="index"
|
||||
class="flex items-center gap-0.5"
|
||||
:class="req.met ? 'text-[var(--ui-success)]' : 'text-[var(--ui-text-muted)]'"
|
||||
>
|
||||
<UIcon :name="req.met ? 'i-heroicons-check-circle' : 'i-heroicons-x-circle'" class="size-4 shrink-0" />
|
||||
|
||||
<span class="text-xs font-light">
|
||||
{{ req.text }}
|
||||
<span class="sr-only">
|
||||
{{ req.met ? ' - Requirement met' : ' - Requirement not met' }}
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
const show = ref(false)
|
||||
const password = ref('password')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UInput
|
||||
v-model="password"
|
||||
placeholder="Password"
|
||||
:type="show ? 'text' : 'password'"
|
||||
:ui="{ trailing: 'pr-0.5' }"
|
||||
>
|
||||
<template #trailing>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="link"
|
||||
size="sm"
|
||||
:icon="show ? 'i-heroicons-eye-slash' : 'i-heroicons-eye'"
|
||||
aria-label="show ? 'Hide password' : 'Show password'"
|
||||
:aria-pressed="show"
|
||||
aria-controls="password"
|
||||
@click="show = !show"
|
||||
/>
|
||||
</template>
|
||||
</UInput>
|
||||
</template>
|
||||
@@ -32,7 +32,7 @@ const groups = computed(() => [{
|
||||
:loading="status === 'pending'"
|
||||
:groups="groups"
|
||||
placeholder="Search users..."
|
||||
class="h-96"
|
||||
class="h-80"
|
||||
/>
|
||||
</template>
|
||||
</UModal>
|
||||
|
||||
@@ -37,22 +37,22 @@ const items = [
|
||||
children: [
|
||||
{
|
||||
label: 'defineShortcuts',
|
||||
icon: 'i-heroicons-document-text-20-solid',
|
||||
icon: 'i-heroicons-document-text',
|
||||
description: 'Define shortcuts for your application.'
|
||||
},
|
||||
{
|
||||
label: 'useModal',
|
||||
icon: 'i-heroicons-document-text-20-solid',
|
||||
icon: 'i-heroicons-document-text',
|
||||
description: 'Display a modal within your application.'
|
||||
},
|
||||
{
|
||||
label: 'useSlideover',
|
||||
icon: 'i-heroicons-document-text-20-solid',
|
||||
icon: 'i-heroicons-document-text',
|
||||
description: 'Display a slideover within your application.'
|
||||
},
|
||||
{
|
||||
label: 'useToast',
|
||||
icon: 'i-heroicons-document-text-20-solid',
|
||||
icon: 'i-heroicons-document-text',
|
||||
description: 'Display a toast within your application.'
|
||||
}
|
||||
]
|
||||
@@ -63,45 +63,50 @@ const items = [
|
||||
children: [
|
||||
{
|
||||
label: 'Link',
|
||||
icon: 'i-heroicons-document-text-20-solid',
|
||||
icon: 'i-heroicons-document-text',
|
||||
description: 'Use NuxtLink with superpowers.'
|
||||
},
|
||||
{
|
||||
label: 'Modal',
|
||||
icon: 'i-heroicons-document-text-20-solid',
|
||||
icon: 'i-heroicons-document-text',
|
||||
description: 'Display a modal within your application.'
|
||||
},
|
||||
{
|
||||
label: 'NavigationMenu',
|
||||
icon: 'i-heroicons-document-text-20-solid',
|
||||
icon: 'i-heroicons-document-text',
|
||||
description: 'Display a list of links.'
|
||||
},
|
||||
{
|
||||
label: 'Pagination',
|
||||
icon: 'i-heroicons-document-text-20-solid',
|
||||
icon: 'i-heroicons-document-text',
|
||||
description: 'Display a list of pages.'
|
||||
},
|
||||
{
|
||||
label: 'Popover',
|
||||
icon: 'i-heroicons-document-text-20-solid',
|
||||
icon: 'i-heroicons-document-text',
|
||||
description: 'Display a non-modal dialog that floats around a trigger element.'
|
||||
},
|
||||
{
|
||||
label: 'Progress',
|
||||
icon: 'i-heroicons-document-text-20-solid',
|
||||
icon: 'i-heroicons-document-text',
|
||||
description: 'Show a horizontal bar to indicate task progression.'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const active = ref('0')
|
||||
const active = ref()
|
||||
|
||||
// Note: This is for demonstration purposes only. Don't do this at home.
|
||||
onMounted(() => {
|
||||
setInterval(() => {
|
||||
active.value = String((Number(active.value) + 1) % items.length)
|
||||
}, 2000)
|
||||
defineShortcuts({
|
||||
1: () => {
|
||||
active.value = '0'
|
||||
},
|
||||
2: () => {
|
||||
active.value = '1'
|
||||
},
|
||||
3: () => {
|
||||
active.value = '2'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -23,8 +23,8 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
|
||||
<UAvatar
|
||||
v-if="modelValue"
|
||||
v-bind="modelValue.avatar"
|
||||
:size="ui.itemLeadingAvatarSize()"
|
||||
:class="ui.itemLeadingAvatar()"
|
||||
:size="ui.leadingAvatarSize()"
|
||||
:class="ui.leadingAvatar()"
|
||||
/>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
|
||||
@@ -29,8 +29,8 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
|
||||
<UAvatar
|
||||
v-if="modelValue"
|
||||
v-bind="modelValue.avatar"
|
||||
:size="ui.itemLeadingAvatarSize()"
|
||||
:class="ui.itemLeadingAvatar()"
|
||||
:size="ui.leadingAvatarSize()"
|
||||
:class="ui.leadingAvatar()"
|
||||
/>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
|
||||
@@ -25,15 +25,15 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
|
||||
<UAvatar
|
||||
v-if="modelValue"
|
||||
v-bind="modelValue.avatar"
|
||||
:size="ui.itemLeadingAvatarSize()"
|
||||
:class="ui.itemLeadingAvatar()"
|
||||
:size="ui.leadingAvatarSize()"
|
||||
:class="ui.leadingAvatar()"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #item-label="{ item }">
|
||||
{{ item.label }}
|
||||
|
||||
<span class="text-[--ui-text-muted]">
|
||||
<span class="text-[var(--ui-text-muted)]">
|
||||
{{ item.email }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
|
||||
const selected = ref('Backlog')
|
||||
const value = ref('Backlog')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu
|
||||
v-model="selected"
|
||||
v-model="value"
|
||||
:items="items"
|
||||
:ui="{
|
||||
trailingIcon: 'group-data-[state=open]:rotate-180 transition-transform duration-200'
|
||||
}"
|
||||
class="w-48"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -25,18 +25,9 @@ const items = ref([
|
||||
}
|
||||
}
|
||||
])
|
||||
const selected = ref(items.value[0])
|
||||
const value = ref(items.value[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :items="items" class="w-40">
|
||||
<template #leading="{ modelValue, ui }">
|
||||
<UAvatar
|
||||
v-if="modelValue"
|
||||
v-bind="modelValue.avatar"
|
||||
:size="ui.itemLeadingAvatarSize()"
|
||||
:class="ui.itemLeadingAvatar()"
|
||||
/>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
<USelectMenu v-model="value" :avatar="value?.avatar" :items="items" class="w-48" />
|
||||
</template>
|
||||
|
||||
@@ -22,11 +22,11 @@ const items = ref([
|
||||
}
|
||||
}
|
||||
])
|
||||
const selected = ref(items.value[0])
|
||||
const value = ref(items.value[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :items="items" class="w-40">
|
||||
<USelectMenu v-model="value" :items="items" class="w-48">
|
||||
<template #leading="{ modelValue, ui }">
|
||||
<UChip
|
||||
v-if="modelValue"
|
||||
|
||||
@@ -21,9 +21,9 @@ const items = ref([
|
||||
icon: 'i-heroicons-check-circle'
|
||||
}
|
||||
])
|
||||
const selected = ref(items.value[0])
|
||||
const value = ref(items.value[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :icon="selected?.icon" :items="items" class="w-40" />
|
||||
<USelectMenu v-model="value" :icon="value?.icon" :items="items" class="w-48" />
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
const open = ref(false)
|
||||
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
|
||||
const selected = ref('Backlog')
|
||||
const value = ref('Backlog')
|
||||
|
||||
defineShortcuts({
|
||||
o: () => open.value = !open.value
|
||||
@@ -9,5 +9,5 @@ defineShortcuts({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" v-model:open="open" :items="items" />
|
||||
<USelectMenu v-model="value" v-model:open="open" :items="items" class="w-48" />
|
||||
</template>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
const searchTerm = ref('D')
|
||||
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
|
||||
const selected = ref('Backlog')
|
||||
const value = ref('Backlog')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" v-model:search-term="searchTerm" :items="items" />
|
||||
<USelectMenu v-model="value" v-model:search-term="searchTerm" :items="items" class="w-48" />
|
||||
</template>
|
||||
|
||||
@@ -27,8 +27,8 @@ function getUserAvatar(value: string) {
|
||||
<UAvatar
|
||||
v-if="modelValue"
|
||||
v-bind="getUserAvatar(modelValue)"
|
||||
:size="ui.itemLeadingAvatarSize()"
|
||||
:class="ui.itemLeadingAvatar()"
|
||||
:size="ui.leadingAvatarSize()"
|
||||
:class="ui.leadingAvatar()"
|
||||
/>
|
||||
</template>
|
||||
</USelect>
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
|
||||
const value = ref('Backlog')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelect
|
||||
default-value="Backlog"
|
||||
v-model="value"
|
||||
:items="items"
|
||||
:ui="{
|
||||
trailingIcon: 'group-data-[state=open]:rotate-180 transition-transform duration-200'
|
||||
}"
|
||||
class="w-48"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -25,21 +25,11 @@ const items = ref([
|
||||
}
|
||||
}
|
||||
])
|
||||
const value = ref(items.value[0]?.value)
|
||||
|
||||
function getAvatar(value: string) {
|
||||
return items.value.find(item => item.value === value)?.avatar
|
||||
}
|
||||
const avatar = computed(() => items.value.find(item => item.value === value.value)?.avatar)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelect default-value="benjamincanac" :items="items" class="w-40">
|
||||
<template #leading="{ modelValue, ui }">
|
||||
<UAvatar
|
||||
v-if="modelValue"
|
||||
v-bind="getAvatar(modelValue)"
|
||||
:size="ui.itemLeadingAvatarSize()"
|
||||
:class="ui.itemLeadingAvatar()"
|
||||
/>
|
||||
</template>
|
||||
</USelect>
|
||||
<USelect v-model="value" :avatar="avatar" :items="items" class="w-48" />
|
||||
</template>
|
||||
|
||||
@@ -22,6 +22,7 @@ const items = ref([
|
||||
}
|
||||
}
|
||||
])
|
||||
const value = ref(items.value[0]?.value)
|
||||
|
||||
function getChip(value: string) {
|
||||
return items.value.find(item => item.value === value)?.chip
|
||||
@@ -29,7 +30,7 @@ function getChip(value: string) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelect default-value="bug" :items="items" class="w-40">
|
||||
<USelect v-model="value" :items="items" class="w-48">
|
||||
<template #leading="{ modelValue, ui }">
|
||||
<UChip
|
||||
v-if="modelValue"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
const selected = ref('backlog')
|
||||
const items = ref([
|
||||
{
|
||||
label: 'Backlog',
|
||||
@@ -22,10 +21,11 @@ const items = ref([
|
||||
icon: 'i-heroicons-check-circle'
|
||||
}
|
||||
])
|
||||
const value = ref(items.value[0]?.value)
|
||||
|
||||
const icon = computed(() => items.value.find(item => item.value === selected.value)?.icon)
|
||||
const icon = computed(() => items.value.find(item => item.value === value.value)?.icon)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelect v-model="selected" :icon="icon" :items="items" class="w-40" />
|
||||
<USelect v-model="value" :icon="icon" :items="items" class="w-48" />
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
const open = ref(false)
|
||||
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
|
||||
const value = ref('Backlog')
|
||||
|
||||
defineShortcuts({
|
||||
o: () => open.value = !open.value
|
||||
@@ -8,5 +9,5 @@ defineShortcuts({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelect v-model:open="open" default-value="Backlog" :items="items" />
|
||||
<USelect v-model="value" v-model:open="open" :items="items" class="w-48" />
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<script setup lang="ts">
|
||||
import { h, resolveComponent } from 'vue'
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
|
||||
const UBadge = resolveComponent('UBadge')
|
||||
|
||||
type Payment = {
|
||||
id: string
|
||||
date: string
|
||||
status: 'paid' | 'failed' | 'refunded'
|
||||
email: string
|
||||
amount: number
|
||||
}
|
||||
|
||||
const data = ref<Payment[]>([{
|
||||
id: '4600',
|
||||
date: '2024-03-11T15:30:00',
|
||||
status: 'paid',
|
||||
email: 'james.anderson@example.com',
|
||||
amount: 594
|
||||
}, {
|
||||
id: '4599',
|
||||
date: '2024-03-11T10:10:00',
|
||||
status: 'failed',
|
||||
email: 'mia.white@example.com',
|
||||
amount: 276
|
||||
}, {
|
||||
id: '4598',
|
||||
date: '2024-03-11T08:50:00',
|
||||
status: 'refunded',
|
||||
email: 'william.brown@example.com',
|
||||
amount: 315
|
||||
}, {
|
||||
id: '4597',
|
||||
date: '2024-03-10T19:45:00',
|
||||
status: 'paid',
|
||||
email: 'emma.davis@example.com',
|
||||
amount: 529
|
||||
}, {
|
||||
id: '4596',
|
||||
date: '2024-03-10T15:55:00',
|
||||
status: 'paid',
|
||||
email: 'ethan.harris@example.com',
|
||||
amount: 639
|
||||
}])
|
||||
|
||||
const columns: TableColumn<Payment>[] = [{
|
||||
accessorKey: 'id',
|
||||
header: '#',
|
||||
cell: ({ row }) => `#${row.getValue('id')}`
|
||||
}, {
|
||||
accessorKey: 'date',
|
||||
header: 'Date',
|
||||
cell: ({ row }) => {
|
||||
return new Date(row.getValue('date')).toLocaleString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
})
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'status',
|
||||
header: 'Status',
|
||||
cell: ({ row }) => {
|
||||
const color = ({
|
||||
paid: 'success' as const,
|
||||
failed: 'error' as const,
|
||||
refunded: 'neutral' as const
|
||||
})[row.getValue('status') as string]
|
||||
|
||||
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () => row.getValue('status'))
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'email',
|
||||
header: 'Email'
|
||||
}, {
|
||||
accessorKey: 'amount',
|
||||
header: () => h('div', { class: 'text-right' }, 'Amount'),
|
||||
cell: ({ row }) => {
|
||||
const amount = Number.parseFloat(row.getValue('amount'))
|
||||
|
||||
const formatted = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'EUR'
|
||||
}).format(amount)
|
||||
|
||||
return h('div', { class: 'text-right font-medium' }, formatted)
|
||||
}
|
||||
}]
|
||||
|
||||
const table = useTemplateRef('table')
|
||||
|
||||
const columnFilters = ref([{
|
||||
id: 'email',
|
||||
value: 'james'
|
||||
}])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex px-4 py-3.5 border-b border-[var(--ui-border-accented)]">
|
||||
<UInput
|
||||
:model-value="(table?.tableApi?.getColumn('email')?.getFilterValue() as string)"
|
||||
class="max-w-sm"
|
||||
placeholder="Filter emails..."
|
||||
@update:model-value="table?.tableApi?.getColumn('email')?.setFilterValue($event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<UTable
|
||||
ref="table"
|
||||
v-model:column-filters="columnFilters"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,114 @@
|
||||
<script setup lang="ts">
|
||||
import { h, resolveComponent } from 'vue'
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
import type { Column } from '@tanstack/vue-table'
|
||||
|
||||
const UBadge = resolveComponent('UBadge')
|
||||
const UButton = resolveComponent('UButton')
|
||||
|
||||
type Payment = {
|
||||
id: string
|
||||
date: string
|
||||
status: 'paid' | 'failed' | 'refunded'
|
||||
email: string
|
||||
amount: number
|
||||
}
|
||||
|
||||
const data = ref<Payment[]>([{
|
||||
id: '46000000000000000000000000000000000000000',
|
||||
date: '2024-03-11T15:30:00',
|
||||
status: 'paid',
|
||||
email: 'james.anderson@example.com',
|
||||
amount: 594000
|
||||
}, {
|
||||
id: '45990000000000000000000000000000000000000',
|
||||
date: '2024-03-11T10:10:00',
|
||||
status: 'failed',
|
||||
email: 'mia.white@example.com',
|
||||
amount: 276000
|
||||
}, {
|
||||
id: '45980000000000000000000000000000000000000',
|
||||
date: '2024-03-11T08:50:00',
|
||||
status: 'refunded',
|
||||
email: 'william.brown@example.com',
|
||||
amount: 315000
|
||||
}, {
|
||||
id: '45970000000000000000000000000000000000000',
|
||||
date: '2024-03-10T19:45:00',
|
||||
status: 'paid',
|
||||
email: 'emma.davis@example.com',
|
||||
amount: 5290000
|
||||
}, {
|
||||
id: '45960000000000000000000000000000000000000',
|
||||
date: '2024-03-10T15:55:00',
|
||||
status: 'paid',
|
||||
email: 'ethan.harris@example.com',
|
||||
amount: 639000
|
||||
}])
|
||||
|
||||
const columns: TableColumn<Payment>[] = [{
|
||||
accessorKey: 'id',
|
||||
header: ({ column }) => getHeader(column, 'ID', 'left'),
|
||||
cell: ({ row }) => `#${row.getValue('id')}`
|
||||
}, {
|
||||
accessorKey: 'date',
|
||||
header: ({ column }) => getHeader(column, 'Date', 'left')
|
||||
}, {
|
||||
accessorKey: 'status',
|
||||
header: ({ column }) => getHeader(column, 'Status', 'left'),
|
||||
cell: ({ row }) => {
|
||||
const color = ({
|
||||
paid: 'success' as const,
|
||||
failed: 'error' as const,
|
||||
refunded: 'neutral' as const
|
||||
})[row.getValue('status') as string]
|
||||
|
||||
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () => row.getValue('status'))
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'email',
|
||||
header: ({ column }) => getHeader(column, 'Email', 'left')
|
||||
}, {
|
||||
accessorKey: 'amount',
|
||||
header: ({ column }) => h('div', { class: 'text-right' }, getHeader(column, 'Amount', 'right')),
|
||||
cell: ({ row }) => {
|
||||
const amount = Number.parseFloat(row.getValue('amount'))
|
||||
|
||||
const formatted = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'EUR'
|
||||
}).format(amount)
|
||||
|
||||
return h('div', { class: 'text-right font-medium' }, formatted)
|
||||
}
|
||||
}]
|
||||
|
||||
function getHeader(column: Column<Payment>, label: string, position: 'left' | 'right') {
|
||||
const isPinned = column.getIsPinned()
|
||||
|
||||
return h(UButton, {
|
||||
color: 'neutral',
|
||||
variant: 'ghost',
|
||||
label,
|
||||
icon: isPinned ? 'i-heroicons-star-20-solid' : 'i-heroicons-star',
|
||||
class: '-mx-2.5',
|
||||
onClick() {
|
||||
column.pin(isPinned === position ? false : position)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const columnPinning = ref({
|
||||
left: [],
|
||||
right: ['amount']
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable
|
||||
v-model:column-pinning="columnPinning"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
class="flex-1"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,118 @@
|
||||
<script setup lang="ts">
|
||||
import { h, resolveComponent } from 'vue'
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
|
||||
const UBadge = resolveComponent('UBadge')
|
||||
const UButton = resolveComponent('UButton')
|
||||
|
||||
type Payment = {
|
||||
id: string
|
||||
date: string
|
||||
status: 'paid' | 'failed' | 'refunded'
|
||||
email: string
|
||||
amount: number
|
||||
}
|
||||
|
||||
const data = ref<Payment[]>([{
|
||||
id: '4600',
|
||||
date: '2024-03-11T15:30:00',
|
||||
status: 'paid',
|
||||
email: 'james.anderson@example.com',
|
||||
amount: 594
|
||||
}, {
|
||||
id: '4599',
|
||||
date: '2024-03-11T10:10:00',
|
||||
status: 'failed',
|
||||
email: 'mia.white@example.com',
|
||||
amount: 276
|
||||
}, {
|
||||
id: '4598',
|
||||
date: '2024-03-11T08:50:00',
|
||||
status: 'refunded',
|
||||
email: 'william.brown@example.com',
|
||||
amount: 315
|
||||
}, {
|
||||
id: '4597',
|
||||
date: '2024-03-10T19:45:00',
|
||||
status: 'paid',
|
||||
email: 'emma.davis@example.com',
|
||||
amount: 529
|
||||
}, {
|
||||
id: '4596',
|
||||
date: '2024-03-10T15:55:00',
|
||||
status: 'paid',
|
||||
email: 'ethan.harris@example.com',
|
||||
amount: 639
|
||||
}])
|
||||
|
||||
const columns: TableColumn<Payment>[] = [{
|
||||
accessorKey: 'id',
|
||||
header: '#',
|
||||
cell: ({ row }) => `#${row.getValue('id')}`
|
||||
}, {
|
||||
accessorKey: 'date',
|
||||
header: 'Date',
|
||||
cell: ({ row }) => {
|
||||
return new Date(row.getValue('date')).toLocaleString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
})
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'status',
|
||||
header: 'Status',
|
||||
cell: ({ row }) => {
|
||||
const color = ({
|
||||
paid: 'success' as const,
|
||||
failed: 'error' as const,
|
||||
refunded: 'neutral' as const
|
||||
})[row.getValue('status') as string]
|
||||
|
||||
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () => row.getValue('status'))
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'email',
|
||||
header: ({ column }) => {
|
||||
const isSorted = column.getIsSorted()
|
||||
|
||||
return h(UButton, {
|
||||
color: 'neutral',
|
||||
variant: 'ghost',
|
||||
label: 'Email',
|
||||
icon: isSorted ? (isSorted === 'asc' ? 'i-heroicons-bars-arrow-up-20-solid' : 'i-heroicons-bars-arrow-down-20-solid') : 'i-heroicons-arrows-up-down-20-solid',
|
||||
class: '-mx-2.5',
|
||||
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc')
|
||||
})
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'amount',
|
||||
header: () => h('div', { class: 'text-right' }, 'Amount'),
|
||||
cell: ({ row }) => {
|
||||
const amount = Number.parseFloat(row.getValue('amount'))
|
||||
|
||||
const formatted = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'EUR'
|
||||
}).format(amount)
|
||||
|
||||
return h('div', { class: 'text-right font-medium' }, formatted)
|
||||
}
|
||||
}]
|
||||
|
||||
const sorting = ref([{
|
||||
id: 'email',
|
||||
desc: false
|
||||
}])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable
|
||||
v-model:sorting="sorting"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
class="flex-1"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,150 @@
|
||||
<script setup lang="ts">
|
||||
import { h, resolveComponent } from 'vue'
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
import type { Column } from '@tanstack/vue-table'
|
||||
|
||||
const UBadge = resolveComponent('UBadge')
|
||||
const UButton = resolveComponent('UButton')
|
||||
const UDropdownMenu = resolveComponent('UDropdownMenu')
|
||||
|
||||
type Payment = {
|
||||
id: string
|
||||
date: string
|
||||
status: 'paid' | 'failed' | 'refunded'
|
||||
email: string
|
||||
amount: number
|
||||
}
|
||||
|
||||
const data = ref<Payment[]>([{
|
||||
id: '4600',
|
||||
date: '2024-03-11T15:30:00',
|
||||
status: 'paid',
|
||||
email: 'james.anderson@example.com',
|
||||
amount: 594
|
||||
}, {
|
||||
id: '4599',
|
||||
date: '2024-03-11T10:10:00',
|
||||
status: 'failed',
|
||||
email: 'mia.white@example.com',
|
||||
amount: 276
|
||||
}, {
|
||||
id: '4598',
|
||||
date: '2024-03-11T08:50:00',
|
||||
status: 'refunded',
|
||||
email: 'william.brown@example.com',
|
||||
amount: 315
|
||||
}, {
|
||||
id: '4597',
|
||||
date: '2024-03-10T19:45:00',
|
||||
status: 'paid',
|
||||
email: 'emma.davis@example.com',
|
||||
amount: 529
|
||||
}, {
|
||||
id: '4596',
|
||||
date: '2024-03-10T15:55:00',
|
||||
status: 'paid',
|
||||
email: 'ethan.harris@example.com',
|
||||
amount: 639
|
||||
}])
|
||||
|
||||
const columns: TableColumn<Payment>[] = [{
|
||||
accessorKey: 'id',
|
||||
header: ({ column }) => getHeader(column, 'ID'),
|
||||
cell: ({ row }) => `#${row.getValue('id')}`
|
||||
}, {
|
||||
accessorKey: 'date',
|
||||
header: ({ column }) => getHeader(column, 'Date'),
|
||||
cell: ({ row }) => {
|
||||
return new Date(row.getValue('date')).toLocaleString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
})
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'status',
|
||||
header: ({ column }) => getHeader(column, 'Status'),
|
||||
cell: ({ row }) => {
|
||||
const color = ({
|
||||
paid: 'success' as const,
|
||||
failed: 'error' as const,
|
||||
refunded: 'neutral' as const
|
||||
})[row.getValue('status') as string]
|
||||
|
||||
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () => row.getValue('status'))
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'email',
|
||||
header: ({ column }) => getHeader(column, 'Email')
|
||||
}, {
|
||||
accessorKey: 'amount',
|
||||
header: ({ column }) => h('div', { class: 'text-right' }, getHeader(column, 'Amount')),
|
||||
cell: ({ row }) => {
|
||||
const amount = Number.parseFloat(row.getValue('amount'))
|
||||
|
||||
const formatted = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'EUR'
|
||||
}).format(amount)
|
||||
|
||||
return h('div', { class: 'text-right font-medium' }, formatted)
|
||||
}
|
||||
}]
|
||||
|
||||
function getHeader(column: Column<Payment>, label: string) {
|
||||
const isSorted = column.getIsSorted()
|
||||
|
||||
return h(UDropdownMenu, {
|
||||
content: {
|
||||
align: 'start'
|
||||
},
|
||||
items: [{
|
||||
label: 'Asc',
|
||||
type: 'checkbox',
|
||||
icon: 'i-heroicons-bars-arrow-up-20-solid',
|
||||
checked: isSorted === 'asc',
|
||||
onSelect: () => {
|
||||
if (isSorted === 'asc') {
|
||||
column.clearSorting()
|
||||
} else {
|
||||
column.toggleSorting(false)
|
||||
}
|
||||
}
|
||||
}, {
|
||||
label: 'Desc',
|
||||
icon: 'i-heroicons-bars-arrow-down-20-solid',
|
||||
type: 'checkbox',
|
||||
checked: isSorted === 'desc',
|
||||
onSelect: () => {
|
||||
if (isSorted === 'desc') {
|
||||
column.clearSorting()
|
||||
} else {
|
||||
column.toggleSorting(true)
|
||||
}
|
||||
}
|
||||
}]
|
||||
}, () => h(UButton, {
|
||||
color: 'neutral',
|
||||
variant: 'ghost',
|
||||
label,
|
||||
icon: isSorted ? (isSorted === 'asc' ? 'i-heroicons-bars-arrow-up-20-solid' : 'i-heroicons-bars-arrow-down-20-solid') : 'i-heroicons-arrows-up-down-20-solid',
|
||||
class: '-mx-2.5 data-[state=open]:bg-[var(--ui-bg-elevated)]'
|
||||
}))
|
||||
}
|
||||
|
||||
const sorting = ref([{
|
||||
id: 'id',
|
||||
desc: false
|
||||
}])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable
|
||||
v-model:sorting="sorting"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
class="flex-1"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,134 @@
|
||||
<script setup lang="ts">
|
||||
import { h, resolveComponent } from 'vue'
|
||||
import { upperFirst } from 'scule'
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
|
||||
const UBadge = resolveComponent('UBadge')
|
||||
|
||||
type Payment = {
|
||||
id: string
|
||||
date: string
|
||||
status: 'paid' | 'failed' | 'refunded'
|
||||
email: string
|
||||
amount: number
|
||||
}
|
||||
|
||||
const data = ref<Payment[]>([{
|
||||
id: '4600',
|
||||
date: '2024-03-11T15:30:00',
|
||||
status: 'paid',
|
||||
email: 'james.anderson@example.com',
|
||||
amount: 594
|
||||
}, {
|
||||
id: '4599',
|
||||
date: '2024-03-11T10:10:00',
|
||||
status: 'failed',
|
||||
email: 'mia.white@example.com',
|
||||
amount: 276
|
||||
}, {
|
||||
id: '4598',
|
||||
date: '2024-03-11T08:50:00',
|
||||
status: 'refunded',
|
||||
email: 'william.brown@example.com',
|
||||
amount: 315
|
||||
}, {
|
||||
id: '4597',
|
||||
date: '2024-03-10T19:45:00',
|
||||
status: 'paid',
|
||||
email: 'emma.davis@example.com',
|
||||
amount: 529
|
||||
}, {
|
||||
id: '4596',
|
||||
date: '2024-03-10T15:55:00',
|
||||
status: 'paid',
|
||||
email: 'ethan.harris@example.com',
|
||||
amount: 639
|
||||
}])
|
||||
|
||||
const columns: TableColumn<Payment>[] = [{
|
||||
accessorKey: 'id',
|
||||
header: '#',
|
||||
cell: ({ row }) => `#${row.getValue('id')}`
|
||||
}, {
|
||||
accessorKey: 'date',
|
||||
header: 'Date',
|
||||
cell: ({ row }) => {
|
||||
return new Date(row.getValue('date')).toLocaleString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
})
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'status',
|
||||
header: 'Status',
|
||||
cell: ({ row }) => {
|
||||
const color = ({
|
||||
paid: 'success' as const,
|
||||
failed: 'error' as const,
|
||||
refunded: 'neutral' as const
|
||||
})[row.getValue('status') as string]
|
||||
|
||||
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () => row.getValue('status'))
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'email',
|
||||
header: 'Email'
|
||||
}, {
|
||||
accessorKey: 'amount',
|
||||
header: () => h('div', { class: 'text-right' }, 'Amount'),
|
||||
cell: ({ row }) => {
|
||||
const amount = Number.parseFloat(row.getValue('amount'))
|
||||
|
||||
const formatted = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'EUR'
|
||||
}).format(amount)
|
||||
|
||||
return h('div', { class: 'text-right font-medium' }, formatted)
|
||||
}
|
||||
}]
|
||||
|
||||
const table = useTemplateRef('table')
|
||||
|
||||
const columnVisibility = ref({
|
||||
id: false
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex justify-end px-4 py-3.5 border-b border-[var(--ui-border-accented)]">
|
||||
<UDropdownMenu
|
||||
:items="table?.tableApi?.getAllColumns().filter(column => column.getCanHide()).map(column => ({
|
||||
label: upperFirst(column.id),
|
||||
type: 'checkbox' as const,
|
||||
checked: column.getIsVisible(),
|
||||
onUpdateChecked(checked: boolean) {
|
||||
table?.tableApi?.getColumn(column.id)?.toggleVisibility(!!checked)
|
||||
},
|
||||
onSelect(e?: Event) {
|
||||
e?.preventDefault()
|
||||
}
|
||||
}))"
|
||||
:content="{ align: 'end' }"
|
||||
>
|
||||
<UButton
|
||||
label="Columns"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
trailing-icon="i-heroicons-chevron-down-20-solid"
|
||||
/>
|
||||
</UDropdownMenu>
|
||||
</div>
|
||||
|
||||
<UTable
|
||||
ref="table"
|
||||
v-model:column-visibility="columnVisibility"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,96 @@
|
||||
<script setup lang="ts">
|
||||
import { h, resolveComponent } from 'vue'
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
|
||||
const UBadge = resolveComponent('UBadge')
|
||||
|
||||
type Payment = {
|
||||
id: string
|
||||
date: string
|
||||
status: 'paid' | 'failed' | 'refunded'
|
||||
email: string
|
||||
amount: number
|
||||
}
|
||||
|
||||
const data = ref<Payment[]>([{
|
||||
id: '4600',
|
||||
date: '2024-03-11T15:30:00',
|
||||
status: 'paid',
|
||||
email: 'james.anderson@example.com',
|
||||
amount: 594
|
||||
}, {
|
||||
id: '4599',
|
||||
date: '2024-03-11T10:10:00',
|
||||
status: 'failed',
|
||||
email: 'mia.white@example.com',
|
||||
amount: 276
|
||||
}, {
|
||||
id: '4598',
|
||||
date: '2024-03-11T08:50:00',
|
||||
status: 'refunded',
|
||||
email: 'william.brown@example.com',
|
||||
amount: 315
|
||||
}, {
|
||||
id: '4597',
|
||||
date: '2024-03-10T19:45:00',
|
||||
status: 'paid',
|
||||
email: 'emma.davis@example.com',
|
||||
amount: 529
|
||||
}, {
|
||||
id: '4596',
|
||||
date: '2024-03-10T15:55:00',
|
||||
status: 'paid',
|
||||
email: 'ethan.harris@example.com',
|
||||
amount: 639
|
||||
}])
|
||||
|
||||
const columns: TableColumn<Payment>[] = [{
|
||||
accessorKey: 'id',
|
||||
header: '#',
|
||||
cell: ({ row }) => `#${row.getValue('id')}`
|
||||
}, {
|
||||
accessorKey: 'date',
|
||||
header: 'Date',
|
||||
cell: ({ row }) => {
|
||||
return new Date(row.getValue('date')).toLocaleString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
})
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'status',
|
||||
header: 'Status',
|
||||
cell: ({ row }) => {
|
||||
const color = ({
|
||||
paid: 'success' as const,
|
||||
failed: 'error' as const,
|
||||
refunded: 'neutral' as const
|
||||
})[row.getValue('status') as string]
|
||||
|
||||
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () => row.getValue('status'))
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'email',
|
||||
header: 'Email'
|
||||
}, {
|
||||
accessorKey: 'amount',
|
||||
header: () => h('div', { class: 'text-right' }, 'Amount'),
|
||||
cell: ({ row }) => {
|
||||
const amount = Number.parseFloat(row.getValue('amount'))
|
||||
|
||||
const formatted = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'EUR'
|
||||
}).format(amount)
|
||||
|
||||
return h('div', { class: 'text-right font-medium' }, formatted)
|
||||
}
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :data="data" :columns="columns" class="flex-1" />
|
||||
</template>
|
||||
319
docs/app/components/content/examples/table/TableExample.vue
Normal file
319
docs/app/components/content/examples/table/TableExample.vue
Normal file
@@ -0,0 +1,319 @@
|
||||
<script setup lang="ts">
|
||||
import { h, resolveComponent } from 'vue'
|
||||
import { upperFirst } from 'scule'
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
|
||||
const UButton = resolveComponent('UButton')
|
||||
const UCheckbox = resolveComponent('UCheckbox')
|
||||
const UBadge = resolveComponent('UBadge')
|
||||
const UDropdownMenu = resolveComponent('UDropdownMenu')
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
type Payment = {
|
||||
id: string
|
||||
date: string
|
||||
status: 'paid' | 'failed' | 'refunded'
|
||||
email: string
|
||||
amount: number
|
||||
}
|
||||
|
||||
const data = ref<Payment[]>([{
|
||||
id: '4600',
|
||||
date: '2024-03-11T15:30:00',
|
||||
status: 'paid',
|
||||
email: 'james.anderson@example.com',
|
||||
amount: 594
|
||||
}, {
|
||||
id: '4599',
|
||||
date: '2024-03-11T10:10:00',
|
||||
status: 'failed',
|
||||
email: 'mia.white@example.com',
|
||||
amount: 276
|
||||
}, {
|
||||
id: '4598',
|
||||
date: '2024-03-11T08:50:00',
|
||||
status: 'refunded',
|
||||
email: 'william.brown@example.com',
|
||||
amount: 315
|
||||
}, {
|
||||
id: '4597',
|
||||
date: '2024-03-10T19:45:00',
|
||||
status: 'paid',
|
||||
email: 'emma.davis@example.com',
|
||||
amount: 529
|
||||
}, {
|
||||
id: '4596',
|
||||
date: '2024-03-10T15:55:00',
|
||||
status: 'paid',
|
||||
email: 'ethan.harris@example.com',
|
||||
amount: 639
|
||||
}, {
|
||||
id: '4595',
|
||||
date: '2024-03-10T13:40:00',
|
||||
status: 'refunded',
|
||||
email: 'ava.thomas@example.com',
|
||||
amount: 428
|
||||
}, {
|
||||
id: '4594',
|
||||
date: '2024-03-10T09:15:00',
|
||||
status: 'paid',
|
||||
email: 'michael.wilson@example.com',
|
||||
amount: 683
|
||||
}, {
|
||||
id: '4593',
|
||||
date: '2024-03-09T20:25:00',
|
||||
status: 'failed',
|
||||
email: 'olivia.taylor@example.com',
|
||||
amount: 947
|
||||
}, {
|
||||
id: '4592',
|
||||
date: '2024-03-09T18:45:00',
|
||||
status: 'paid',
|
||||
email: 'benjamin.jackson@example.com',
|
||||
amount: 851
|
||||
}, {
|
||||
id: '4591',
|
||||
date: '2024-03-09T16:05:00',
|
||||
status: 'paid',
|
||||
email: 'sophia.miller@example.com',
|
||||
amount: 762
|
||||
}, {
|
||||
id: '4590',
|
||||
date: '2024-03-09T14:20:00',
|
||||
status: 'paid',
|
||||
email: 'noah.clark@example.com',
|
||||
amount: 573
|
||||
}, {
|
||||
id: '4589',
|
||||
date: '2024-03-09T11:35:00',
|
||||
status: 'failed',
|
||||
email: 'isabella.lee@example.com',
|
||||
amount: 389
|
||||
}, {
|
||||
id: '4588',
|
||||
date: '2024-03-08T22:50:00',
|
||||
status: 'refunded',
|
||||
email: 'liam.walker@example.com',
|
||||
amount: 701
|
||||
}, {
|
||||
id: '4587',
|
||||
date: '2024-03-08T20:15:00',
|
||||
status: 'paid',
|
||||
email: 'charlotte.hall@example.com',
|
||||
amount: 856
|
||||
}, {
|
||||
id: '4586',
|
||||
date: '2024-03-08T17:40:00',
|
||||
status: 'paid',
|
||||
email: 'mason.young@example.com',
|
||||
amount: 492
|
||||
}, {
|
||||
id: '4585',
|
||||
date: '2024-03-08T14:55:00',
|
||||
status: 'failed',
|
||||
email: 'amelia.king@example.com',
|
||||
amount: 637
|
||||
}, {
|
||||
id: '4584',
|
||||
date: '2024-03-08T12:30:00',
|
||||
status: 'paid',
|
||||
email: 'elijah.wright@example.com',
|
||||
amount: 784
|
||||
}, {
|
||||
id: '4583',
|
||||
date: '2024-03-08T09:45:00',
|
||||
status: 'refunded',
|
||||
email: 'harper.scott@example.com',
|
||||
amount: 345
|
||||
}, {
|
||||
id: '4582',
|
||||
date: '2024-03-07T23:10:00',
|
||||
status: 'paid',
|
||||
email: 'evelyn.green@example.com',
|
||||
amount: 918
|
||||
}, {
|
||||
id: '4581',
|
||||
date: '2024-03-07T20:25:00',
|
||||
status: 'paid',
|
||||
email: 'logan.baker@example.com',
|
||||
amount: 567
|
||||
}])
|
||||
|
||||
const columns: TableColumn<Payment>[] = [{
|
||||
id: 'select',
|
||||
header: ({ table }) => h(UCheckbox, {
|
||||
'modelValue': table.getIsAllPageRowsSelected(),
|
||||
'indeterminate': table.getIsSomePageRowsSelected(),
|
||||
'onUpdate:modelValue': (value: boolean) => table.toggleAllPageRowsSelected(!!value),
|
||||
'ariaLabel': 'Select all'
|
||||
}),
|
||||
cell: ({ row }) => h(UCheckbox, {
|
||||
'modelValue': row.getIsSelected(),
|
||||
'onUpdate:modelValue': (value: boolean) => row.toggleSelected(!!value),
|
||||
'ariaLabel': 'Select row'
|
||||
}),
|
||||
enableSorting: false,
|
||||
enableHiding: false
|
||||
}, {
|
||||
accessorKey: 'id',
|
||||
header: '#',
|
||||
cell: ({ row }) => `#${row.getValue('id')}`
|
||||
}, {
|
||||
accessorKey: 'date',
|
||||
header: 'Date',
|
||||
cell: ({ row }) => {
|
||||
return new Date(row.getValue('date')).toLocaleString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
})
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'status',
|
||||
header: 'Status',
|
||||
cell: ({ row }) => {
|
||||
const color = ({
|
||||
paid: 'success' as const,
|
||||
failed: 'error' as const,
|
||||
refunded: 'neutral' as const
|
||||
})[row.getValue('status') as string]
|
||||
|
||||
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () => row.getValue('status'))
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'email',
|
||||
header: ({ column }) => {
|
||||
const isSorted = column.getIsSorted()
|
||||
|
||||
return h(UButton, {
|
||||
color: 'neutral',
|
||||
variant: 'ghost',
|
||||
label: 'Email',
|
||||
icon: isSorted ? (isSorted === 'asc' ? 'i-heroicons-bars-arrow-up-20-solid' : 'i-heroicons-bars-arrow-down-20-solid') : 'i-heroicons-arrows-up-down-20-solid',
|
||||
class: '-mx-2.5',
|
||||
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc')
|
||||
})
|
||||
},
|
||||
cell: ({ row }) => h('div', { class: 'lowercase' }, row.getValue('email'))
|
||||
}, {
|
||||
accessorKey: 'amount',
|
||||
header: () => h('div', { class: 'text-right' }, 'Amount'),
|
||||
cell: ({ row }) => {
|
||||
const amount = Number.parseFloat(row.getValue('amount'))
|
||||
|
||||
const formatted = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'EUR'
|
||||
}).format(amount)
|
||||
|
||||
return h('div', { class: 'text-right font-medium' }, formatted)
|
||||
}
|
||||
}, {
|
||||
id: 'actions',
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const items = [{
|
||||
type: 'label',
|
||||
label: 'Actions'
|
||||
}, {
|
||||
label: 'Copy payment ID',
|
||||
onSelect() {
|
||||
navigator.clipboard.writeText(row.original.id)
|
||||
|
||||
toast.add({
|
||||
title: 'Payment ID copied to clipboard!',
|
||||
color: 'success',
|
||||
icon: 'i-heroicons-check-circle'
|
||||
})
|
||||
}
|
||||
}, {
|
||||
label: row.getIsExpanded() ? 'Collapse' : 'Expand',
|
||||
onSelect() {
|
||||
row.toggleExpanded()
|
||||
}
|
||||
}, {
|
||||
type: 'separator'
|
||||
}, {
|
||||
label: 'View customer'
|
||||
}, {
|
||||
label: 'View payment details'
|
||||
}]
|
||||
|
||||
return h('div', { class: 'text-right' }, h(UDropdownMenu, {
|
||||
content: {
|
||||
align: 'end'
|
||||
},
|
||||
items
|
||||
}, () => h(UButton, {
|
||||
icon: 'i-heroicons-ellipsis-vertical-20-solid',
|
||||
color: 'neutral',
|
||||
variant: 'ghost',
|
||||
class: 'ml-auto'
|
||||
})))
|
||||
}
|
||||
}]
|
||||
|
||||
const table = useTemplateRef('table')
|
||||
|
||||
function randomize() {
|
||||
data.value = [...data.value].sort(() => Math.random() - 0.5)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-1 divide-y divide-[var(--ui-border-accented)]">
|
||||
<div class="flex items-center gap-2 px-4 py-3.5">
|
||||
<UInput
|
||||
:model-value="(table?.tableApi?.getColumn('email')?.getFilterValue() as string)"
|
||||
class="max-w-sm"
|
||||
placeholder="Filter emails..."
|
||||
@update:model-value="table?.tableApi?.getColumn('email')?.setFilterValue($event)"
|
||||
/>
|
||||
|
||||
<UButton color="neutral" label="Randomize" @click="randomize" />
|
||||
|
||||
<UDropdownMenu
|
||||
:items="table?.tableApi?.getAllColumns().filter(column => column.getCanHide()).map(column => ({
|
||||
label: upperFirst(column.id),
|
||||
type: 'checkbox' as const,
|
||||
checked: column.getIsVisible(),
|
||||
onUpdateChecked(checked: boolean) {
|
||||
table?.tableApi?.getColumn(column.id)?.toggleVisibility(!!checked)
|
||||
},
|
||||
onSelect(e?: Event) {
|
||||
e?.preventDefault()
|
||||
}
|
||||
}))"
|
||||
:content="{ align: 'end' }"
|
||||
>
|
||||
<UButton
|
||||
label="Columns"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
trailing-icon="i-heroicons-chevron-down-20-solid"
|
||||
class="ml-auto"
|
||||
/>
|
||||
</UDropdownMenu>
|
||||
</div>
|
||||
|
||||
<UTable
|
||||
ref="table"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
sticky
|
||||
class="h-96"
|
||||
>
|
||||
<template #expanded="{ row }">
|
||||
<pre>{{ row.original }}</pre>
|
||||
</template>
|
||||
</UTable>
|
||||
|
||||
<div class="px-4 py-3.5 text-sm text-[var(--ui-text-muted)]">
|
||||
{{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
|
||||
{{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,55 @@
|
||||
<script setup lang="ts">
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
|
||||
const UAvatar = resolveComponent('UAvatar')
|
||||
|
||||
type User = {
|
||||
id: number
|
||||
name: string
|
||||
username: string
|
||||
email: string
|
||||
avatar: { src: string }
|
||||
company: { name: string }
|
||||
}
|
||||
|
||||
const { data, status } = await useFetch<User[]>('https://jsonplaceholder.typicode.com/users', {
|
||||
transform: (data) => {
|
||||
return data?.map(user => ({
|
||||
...user,
|
||||
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
|
||||
})) || []
|
||||
},
|
||||
lazy: true
|
||||
})
|
||||
|
||||
const columns: TableColumn<User>[] = [{
|
||||
accessorKey: 'id',
|
||||
header: 'ID'
|
||||
}, {
|
||||
accessorKey: 'name',
|
||||
header: 'Name',
|
||||
cell: ({ row }) => {
|
||||
return h('div', { class: 'flex items-center gap-3' }, [
|
||||
h(UAvatar, {
|
||||
...row.original.avatar,
|
||||
size: 'lg'
|
||||
}),
|
||||
h('div', undefined, [
|
||||
h('p', { class: 'font-medium text-[var(--ui-text-highlighted)]' }, row.original.name),
|
||||
h('p', { class: '' }, `@${row.original.username}`)
|
||||
])
|
||||
])
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'email',
|
||||
header: 'Email'
|
||||
}, {
|
||||
accessorKey: 'company',
|
||||
header: 'Company',
|
||||
cell: ({ row }) => row.original.company.name
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :data="data" :columns="columns" :loading="status === 'pending'" class="flex-1" />
|
||||
</template>
|
||||
@@ -0,0 +1,115 @@
|
||||
<script setup lang="ts">
|
||||
import { h, resolveComponent } from 'vue'
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
|
||||
const UBadge = resolveComponent('UBadge')
|
||||
|
||||
type Payment = {
|
||||
id: string
|
||||
date: string
|
||||
status: 'paid' | 'failed' | 'refunded'
|
||||
email: string
|
||||
amount: number
|
||||
}
|
||||
|
||||
const data = ref<Payment[]>([{
|
||||
id: '4600',
|
||||
date: '2024-03-11T15:30:00',
|
||||
status: 'paid',
|
||||
email: 'james.anderson@example.com',
|
||||
amount: 594
|
||||
}, {
|
||||
id: '4599',
|
||||
date: '2024-03-11T10:10:00',
|
||||
status: 'failed',
|
||||
email: 'mia.white@example.com',
|
||||
amount: 276
|
||||
}, {
|
||||
id: '4598',
|
||||
date: '2024-03-11T08:50:00',
|
||||
status: 'refunded',
|
||||
email: 'william.brown@example.com',
|
||||
amount: 315
|
||||
}, {
|
||||
id: '4597',
|
||||
date: '2024-03-10T19:45:00',
|
||||
status: 'paid',
|
||||
email: 'emma.davis@example.com',
|
||||
amount: 529
|
||||
}, {
|
||||
id: '4596',
|
||||
date: '2024-03-10T15:55:00',
|
||||
status: 'paid',
|
||||
email: 'ethan.harris@example.com',
|
||||
amount: 639
|
||||
}])
|
||||
|
||||
const columns: TableColumn<Payment>[] = [{
|
||||
accessorKey: 'id',
|
||||
header: '#',
|
||||
cell: ({ row }) => `#${row.getValue('id')}`
|
||||
}, {
|
||||
accessorKey: 'date',
|
||||
header: 'Date',
|
||||
cell: ({ row }) => {
|
||||
return new Date(row.getValue('date')).toLocaleString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
})
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'status',
|
||||
header: 'Status',
|
||||
cell: ({ row }) => {
|
||||
const color = ({
|
||||
paid: 'success' as const,
|
||||
failed: 'error' as const,
|
||||
refunded: 'neutral' as const
|
||||
})[row.getValue('status') as string]
|
||||
|
||||
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () => row.getValue('status'))
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'email',
|
||||
header: 'Email'
|
||||
}, {
|
||||
accessorKey: 'amount',
|
||||
header: () => h('div', { class: 'text-right' }, 'Amount'),
|
||||
cell: ({ row }) => {
|
||||
const amount = Number.parseFloat(row.getValue('amount'))
|
||||
|
||||
const formatted = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'EUR'
|
||||
}).format(amount)
|
||||
|
||||
return h('div', { class: 'text-right font-medium' }, formatted)
|
||||
}
|
||||
}]
|
||||
|
||||
const table = useTemplateRef('table')
|
||||
|
||||
const globalFilter = ref('45')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex px-4 py-3.5 border-b border-[var(--ui-border-accented)]">
|
||||
<UInput
|
||||
v-model="globalFilter"
|
||||
class="max-w-sm"
|
||||
placeholder="Filter..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<UTable
|
||||
ref="table"
|
||||
v-model:global-filter="globalFilter"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,140 @@
|
||||
<script setup lang="ts">
|
||||
import { h, resolveComponent } from 'vue'
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
import type { Row } from '@tanstack/vue-table'
|
||||
|
||||
const UButton = resolveComponent('UButton')
|
||||
const UBadge = resolveComponent('UBadge')
|
||||
const UDropdownMenu = resolveComponent('UDropdownMenu')
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
type Payment = {
|
||||
id: string
|
||||
date: string
|
||||
status: 'paid' | 'failed' | 'refunded'
|
||||
email: string
|
||||
amount: number
|
||||
}
|
||||
|
||||
const data = ref<Payment[]>([{
|
||||
id: '4600',
|
||||
date: '2024-03-11T15:30:00',
|
||||
status: 'paid',
|
||||
email: 'james.anderson@example.com',
|
||||
amount: 594
|
||||
}, {
|
||||
id: '4599',
|
||||
date: '2024-03-11T10:10:00',
|
||||
status: 'failed',
|
||||
email: 'mia.white@example.com',
|
||||
amount: 276
|
||||
}, {
|
||||
id: '4598',
|
||||
date: '2024-03-11T08:50:00',
|
||||
status: 'refunded',
|
||||
email: 'william.brown@example.com',
|
||||
amount: 315
|
||||
}, {
|
||||
id: '4597',
|
||||
date: '2024-03-10T19:45:00',
|
||||
status: 'paid',
|
||||
email: 'emma.davis@example.com',
|
||||
amount: 529
|
||||
}, {
|
||||
id: '4596',
|
||||
date: '2024-03-10T15:55:00',
|
||||
status: 'paid',
|
||||
email: 'ethan.harris@example.com',
|
||||
amount: 639
|
||||
}])
|
||||
|
||||
const columns: TableColumn<Payment>[] = [{
|
||||
accessorKey: 'id',
|
||||
header: '#',
|
||||
cell: ({ row }) => `#${row.getValue('id')}`
|
||||
}, {
|
||||
accessorKey: 'date',
|
||||
header: 'Date',
|
||||
cell: ({ row }) => {
|
||||
return new Date(row.getValue('date')).toLocaleString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
})
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'status',
|
||||
header: 'Status',
|
||||
cell: ({ row }) => {
|
||||
const color = ({
|
||||
paid: 'success' as const,
|
||||
failed: 'error' as const,
|
||||
refunded: 'neutral' as const
|
||||
})[row.getValue('status') as string]
|
||||
|
||||
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () => row.getValue('status'))
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'email',
|
||||
header: 'Email'
|
||||
}, {
|
||||
accessorKey: 'amount',
|
||||
header: () => h('div', { class: 'text-right' }, 'Amount'),
|
||||
cell: ({ row }) => {
|
||||
const amount = Number.parseFloat(row.getValue('amount'))
|
||||
|
||||
const formatted = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'EUR'
|
||||
}).format(amount)
|
||||
|
||||
return h('div', { class: 'text-right font-medium' }, formatted)
|
||||
}
|
||||
}, {
|
||||
id: 'actions',
|
||||
cell: ({ row }) => {
|
||||
return h('div', { class: 'text-right' }, h(UDropdownMenu, {
|
||||
content: {
|
||||
align: 'end'
|
||||
},
|
||||
items: getRowItems(row)
|
||||
}, () => h(UButton, {
|
||||
icon: 'i-heroicons-ellipsis-vertical-20-solid',
|
||||
color: 'neutral',
|
||||
variant: 'ghost',
|
||||
class: 'ml-auto'
|
||||
})))
|
||||
}
|
||||
}]
|
||||
|
||||
function getRowItems(row: Row<Payment>) {
|
||||
return [{
|
||||
type: 'label',
|
||||
label: 'Actions'
|
||||
}, {
|
||||
label: 'Copy payment ID',
|
||||
onSelect() {
|
||||
navigator.clipboard.writeText(row.original.id)
|
||||
|
||||
toast.add({
|
||||
title: 'Payment ID copied to clipboard!',
|
||||
color: 'success',
|
||||
icon: 'i-heroicons-check-circle'
|
||||
})
|
||||
}
|
||||
}, {
|
||||
type: 'separator'
|
||||
}, {
|
||||
label: 'View customer'
|
||||
}, {
|
||||
label: 'View payment details'
|
||||
}]
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :data="data" :columns="columns" class="flex-1" />
|
||||
</template>
|
||||
@@ -0,0 +1,121 @@
|
||||
<script setup lang="ts">
|
||||
import { h, resolveComponent } from 'vue'
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
|
||||
const UButton = resolveComponent('UButton')
|
||||
const UBadge = resolveComponent('UBadge')
|
||||
|
||||
type Payment = {
|
||||
id: string
|
||||
date: string
|
||||
status: 'paid' | 'failed' | 'refunded'
|
||||
email: string
|
||||
amount: number
|
||||
}
|
||||
|
||||
const data = ref<Payment[]>([{
|
||||
id: '4600',
|
||||
date: '2024-03-11T15:30:00',
|
||||
status: 'paid',
|
||||
email: 'james.anderson@example.com',
|
||||
amount: 594
|
||||
}, {
|
||||
id: '4599',
|
||||
date: '2024-03-11T10:10:00',
|
||||
status: 'failed',
|
||||
email: 'mia.white@example.com',
|
||||
amount: 276
|
||||
}, {
|
||||
id: '4598',
|
||||
date: '2024-03-11T08:50:00',
|
||||
status: 'refunded',
|
||||
email: 'william.brown@example.com',
|
||||
amount: 315
|
||||
}, {
|
||||
id: '4597',
|
||||
date: '2024-03-10T19:45:00',
|
||||
status: 'paid',
|
||||
email: 'emma.davis@example.com',
|
||||
amount: 529
|
||||
}, {
|
||||
id: '4596',
|
||||
date: '2024-03-10T15:55:00',
|
||||
status: 'paid',
|
||||
email: 'ethan.harris@example.com',
|
||||
amount: 639
|
||||
}])
|
||||
|
||||
const columns: TableColumn<Payment>[] = [{
|
||||
id: 'expand',
|
||||
cell: ({ row }) => h(UButton, {
|
||||
color: 'neutral',
|
||||
variant: 'ghost',
|
||||
icon: 'i-heroicons-chevron-down-20-solid',
|
||||
square: true,
|
||||
ui: {
|
||||
leadingIcon: ['transition-transform', row.getIsExpanded() ? 'duration-200 rotate-180' : '']
|
||||
},
|
||||
onClick: () => row.toggleExpanded()
|
||||
})
|
||||
}, {
|
||||
accessorKey: 'id',
|
||||
header: '#',
|
||||
cell: ({ row }) => `#${row.getValue('id')}`
|
||||
}, {
|
||||
accessorKey: 'date',
|
||||
header: 'Date',
|
||||
cell: ({ row }) => {
|
||||
return new Date(row.getValue('date')).toLocaleString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
})
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'status',
|
||||
header: 'Status',
|
||||
cell: ({ row }) => {
|
||||
const color = ({
|
||||
paid: 'success' as const,
|
||||
failed: 'error' as const,
|
||||
refunded: 'neutral' as const
|
||||
})[row.getValue('status') as string]
|
||||
|
||||
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () => row.getValue('status'))
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'email',
|
||||
header: 'Email'
|
||||
}, {
|
||||
accessorKey: 'amount',
|
||||
header: () => h('div', { class: 'text-right' }, 'Amount'),
|
||||
cell: ({ row }) => {
|
||||
const amount = Number.parseFloat(row.getValue('amount'))
|
||||
|
||||
const formatted = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'EUR'
|
||||
}).format(amount)
|
||||
|
||||
return h('div', { class: 'text-right font-medium' }, formatted)
|
||||
}
|
||||
}]
|
||||
|
||||
const expanded = ref({ 1: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable
|
||||
v-model:expanded="expanded"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
:ui="{ tr: 'data-[expanded=true]:bg-[var(--ui-bg-elevated)]/50' }"
|
||||
class="flex-1"
|
||||
>
|
||||
<template #expanded="{ row }">
|
||||
<pre>{{ row.original }}</pre>
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
@@ -0,0 +1,122 @@
|
||||
<script setup lang="ts">
|
||||
import { h, resolveComponent } from 'vue'
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
|
||||
const UCheckbox = resolveComponent('UCheckbox')
|
||||
const UBadge = resolveComponent('UBadge')
|
||||
|
||||
type Payment = {
|
||||
id: string
|
||||
date: string
|
||||
status: 'paid' | 'failed' | 'refunded'
|
||||
email: string
|
||||
amount: number
|
||||
}
|
||||
|
||||
const data = ref<Payment[]>([{
|
||||
id: '4600',
|
||||
date: '2024-03-11T15:30:00',
|
||||
status: 'paid',
|
||||
email: 'james.anderson@example.com',
|
||||
amount: 594
|
||||
}, {
|
||||
id: '4599',
|
||||
date: '2024-03-11T10:10:00',
|
||||
status: 'failed',
|
||||
email: 'mia.white@example.com',
|
||||
amount: 276
|
||||
}, {
|
||||
id: '4598',
|
||||
date: '2024-03-11T08:50:00',
|
||||
status: 'refunded',
|
||||
email: 'william.brown@example.com',
|
||||
amount: 315
|
||||
}, {
|
||||
id: '4597',
|
||||
date: '2024-03-10T19:45:00',
|
||||
status: 'paid',
|
||||
email: 'emma.davis@example.com',
|
||||
amount: 529
|
||||
}, {
|
||||
id: '4596',
|
||||
date: '2024-03-10T15:55:00',
|
||||
status: 'paid',
|
||||
email: 'ethan.harris@example.com',
|
||||
amount: 639
|
||||
}])
|
||||
|
||||
const columns: TableColumn<Payment>[] = [{
|
||||
id: 'select',
|
||||
header: ({ table }) => h(UCheckbox, {
|
||||
'modelValue': table.getIsAllPageRowsSelected(),
|
||||
'indeterminate': table.getIsSomePageRowsSelected(),
|
||||
'onUpdate:modelValue': (value: boolean) => table.toggleAllPageRowsSelected(!!value),
|
||||
'ariaLabel': 'Select all'
|
||||
}),
|
||||
cell: ({ row }) => h(UCheckbox, {
|
||||
'modelValue': row.getIsSelected(),
|
||||
'onUpdate:modelValue': (value: boolean) => row.toggleSelected(!!value),
|
||||
'ariaLabel': 'Select row'
|
||||
})
|
||||
}, {
|
||||
accessorKey: 'date',
|
||||
header: 'Date',
|
||||
cell: ({ row }) => {
|
||||
return new Date(row.getValue('date')).toLocaleString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
})
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'status',
|
||||
header: 'Status',
|
||||
cell: ({ row }) => {
|
||||
const color = ({
|
||||
paid: 'success' as const,
|
||||
failed: 'error' as const,
|
||||
refunded: 'neutral' as const
|
||||
})[row.getValue('status') as string]
|
||||
|
||||
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () => row.getValue('status'))
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'email',
|
||||
header: 'Email'
|
||||
}, {
|
||||
accessorKey: 'amount',
|
||||
header: () => h('div', { class: 'text-right' }, 'Amount'),
|
||||
cell: ({ row }) => {
|
||||
const amount = Number.parseFloat(row.getValue('amount'))
|
||||
|
||||
const formatted = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'EUR'
|
||||
}).format(amount)
|
||||
|
||||
return h('div', { class: 'text-right font-medium' }, formatted)
|
||||
}
|
||||
}]
|
||||
|
||||
const table = useTemplateRef('table')
|
||||
|
||||
const rowSelection = ref({ 1: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-1">
|
||||
<UTable
|
||||
ref="table"
|
||||
v-model:row-selection="rowSelection"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
/>
|
||||
|
||||
<div class="px-4 py-3.5 border-t border-[var(--ui-border-accented)] text-sm text-[var(--ui-text-muted)]">
|
||||
{{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
|
||||
{{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -26,7 +26,7 @@ const state = reactive({
|
||||
<template>
|
||||
<UTabs :items="items" variant="link" class="gap-4 w-full" :ui="{ trigger: 'flex-1' }">
|
||||
<template #account="{ item }">
|
||||
<p class="text-[--ui-text-muted] mb-4">
|
||||
<p class="text-[var(--ui-text-muted)] mb-4">
|
||||
{{ item.description }}
|
||||
</p>
|
||||
|
||||
@@ -43,7 +43,7 @@ const state = reactive({
|
||||
</template>
|
||||
|
||||
<template #password="{ item }">
|
||||
<p class="text-[--ui-text-muted] mb-4">
|
||||
<p class="text-[var(--ui-text-muted)] mb-4">
|
||||
{{ item.description }}
|
||||
</p>
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ const appConfig = useAppConfig()
|
||||
<UFormField
|
||||
label="toaster.duration"
|
||||
size="sm"
|
||||
class="inline-flex ring ring-[--ui-border-accented] rounded"
|
||||
class="inline-flex ring ring-[var(--ui-border-accented)] rounded"
|
||||
:ui="{
|
||||
wrapper: 'bg-[--ui-bg-elevated]/50 rounded-l flex border-r border-[--ui-border-accented]',
|
||||
label: 'text-[--ui-text-muted] px-2 py-1.5',
|
||||
wrapper: 'bg-[var(--ui-bg-elevated)]/50 rounded-l flex border-r border-[var(--ui-border-accented)]',
|
||||
label: 'text-[var(--ui-text-muted)] px-2 py-1.5',
|
||||
container: 'mt-0'
|
||||
}"
|
||||
>
|
||||
|
||||
@@ -7,10 +7,10 @@ const appConfig = useAppConfig()
|
||||
<UFormField
|
||||
label="toaster.expand"
|
||||
size="sm"
|
||||
class="inline-flex ring ring-[--ui-border-accented] rounded"
|
||||
class="inline-flex ring ring-[var(--ui-border-accented)] rounded"
|
||||
:ui="{
|
||||
wrapper: 'bg-[--ui-bg-elevated]/50 rounded-l flex border-r border-[--ui-border-accented]',
|
||||
label: 'text-[--ui-text-muted] px-2 py-1.5',
|
||||
wrapper: 'bg-[var(--ui-bg-elevated)]/50 rounded-l flex border-r border-[var(--ui-border-accented)]',
|
||||
label: 'text-[var(--ui-text-muted)] px-2 py-1.5',
|
||||
container: 'mt-0'
|
||||
}"
|
||||
>
|
||||
|
||||
@@ -10,10 +10,10 @@ const appConfig = useAppConfig()
|
||||
<UFormField
|
||||
label="toaster.position"
|
||||
size="sm"
|
||||
class="inline-flex ring ring-[--ui-border-accented] rounded"
|
||||
class="inline-flex ring ring-[var(--ui-border-accented)] rounded"
|
||||
:ui="{
|
||||
wrapper: 'bg-[--ui-bg-elevated]/50 rounded-l flex border-r border-[--ui-border-accented]',
|
||||
label: 'text-[--ui-text-muted] px-2 py-1.5',
|
||||
wrapper: 'bg-[var(--ui-bg-elevated)]/50 rounded-l flex border-r border-[var(--ui-border-accented)]',
|
||||
label: 'text-[var(--ui-text-muted)] px-2 py-1.5',
|
||||
container: 'mt-0'
|
||||
}"
|
||||
>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
:variant="open ? 'soft' : 'ghost'"
|
||||
square
|
||||
aria-label="Color picker"
|
||||
:ui="{ leadingIcon: 'text-[--ui-primary]' }"
|
||||
:ui="{ leadingIcon: 'text-[var(--ui-primary)]' }"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
:label="color"
|
||||
:chip="color"
|
||||
:selected="primary === color"
|
||||
@select="primary = color"
|
||||
@click="primary = color"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -41,7 +41,7 @@
|
||||
:label="color"
|
||||
:chip="color"
|
||||
:selected="neutral === color"
|
||||
@select="neutral = color"
|
||||
@click="neutral = color"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -58,7 +58,7 @@
|
||||
:label="String(r)"
|
||||
class="justify-center px-0"
|
||||
:selected="radius === r"
|
||||
@select="radius = r"
|
||||
@click="radius = r"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -74,7 +74,7 @@
|
||||
:key="m.label"
|
||||
v-bind="m"
|
||||
:selected="mode === m.label"
|
||||
@select="mode = m.label"
|
||||
@click="mode = m.label"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
@@ -5,13 +5,12 @@
|
||||
:icon="icon"
|
||||
:label="label"
|
||||
:variant="selected ? 'soft' : 'outline'"
|
||||
class="capitalize ring-[--ui-border] rounded-[--ui-radius] text-[11px]"
|
||||
@click.stop.prevent="$emit('select')"
|
||||
class="capitalize ring-[var(--ui-border)] rounded-[var(--ui-radius)] text-[11px]"
|
||||
>
|
||||
<template v-if="chip" #leading>
|
||||
<span
|
||||
class="inline-block w-2 h-2 rounded-full"
|
||||
:class="`bg-[--color-light] dark:bg-[--color-dark]`"
|
||||
:class="`bg-[var(--color-light)] dark:bg-[var(--color-dark)]`"
|
||||
:style="{
|
||||
'--color-light': `var(--color-${chip}-500)`,
|
||||
'--color-dark': `var(--color-${chip}-400)`
|
||||
@@ -28,5 +27,4 @@ defineProps<{
|
||||
chip?: string
|
||||
selected?: boolean
|
||||
}>()
|
||||
defineEmits(['select'])
|
||||
</script>
|
||||
|
||||
@@ -1,28 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import type { NuxtError } from '#app'
|
||||
// import type { ContentSearchFile } from '@nuxt/ui-pro'
|
||||
import colors from 'tailwindcss/colors'
|
||||
import type { ContentSearchFile } from '@nuxt/ui-pro'
|
||||
|
||||
useSeoMeta({
|
||||
title: 'Page not found',
|
||||
description: 'We are sorry but this page could not be found.'
|
||||
})
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
error: NuxtError
|
||||
}>()
|
||||
|
||||
const route = useRoute()
|
||||
// const colorMode = useColorMode()
|
||||
// const { branch } = useContentSource()
|
||||
const appConfig = useAppConfig()
|
||||
const colorMode = useColorMode()
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const { integrity, api } = runtimeConfig.public.content
|
||||
|
||||
const { data: navigation } = await useAsyncData('navigation', () => fetchContentNavigation(), { default: () => [] })
|
||||
const { data: files } = await useLazyFetch<any[]>(`${api.baseURL}/search${integrity ? '.' + integrity : ''}`, { default: () => [] })
|
||||
|
||||
// Computed
|
||||
|
||||
// const color = computed(() => colorMode.value === 'dark' ? '#18181b' : 'white')
|
||||
const { data: files } = await useLazyFetch<ContentSearchFile[]>(`${api.baseURL}/search${integrity ? '-' + integrity : ''}`, { default: () => [] })
|
||||
|
||||
const links = computed(() => {
|
||||
return [{
|
||||
@@ -52,43 +44,52 @@ const links = computed(() => {
|
||||
}].filter(Boolean)
|
||||
})
|
||||
|
||||
// Head
|
||||
const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white')
|
||||
const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`)
|
||||
|
||||
useHead({
|
||||
meta: [
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
|
||||
// { key: 'theme-color', name: 'theme-color', content: color }
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||
{ key: 'theme-color', name: 'theme-color', content: color }
|
||||
],
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/svg+xml', href: '/icon.svg' }
|
||||
],
|
||||
style: [
|
||||
{ innerHTML: radius, id: 'nuxt-ui-radius', tagPriority: -2 }
|
||||
],
|
||||
htmlAttrs: {
|
||||
lang: 'en'
|
||||
}
|
||||
})
|
||||
|
||||
// Provide
|
||||
useSeoMeta({
|
||||
titleTemplate: '%s - Nuxt UI v3',
|
||||
title: String(props.error.statusCode)
|
||||
})
|
||||
|
||||
useServerSeoMeta({
|
||||
ogSiteName: 'Nuxt UI',
|
||||
twitterCard: 'summary_large_image'
|
||||
})
|
||||
|
||||
provide('navigation', navigation)
|
||||
provide('files', files)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UApp>
|
||||
<NuxtLoadingIndicator />
|
||||
<NuxtLoadingIndicator color="#FFF" />
|
||||
|
||||
<Banner />
|
||||
|
||||
<Header :links="links" />
|
||||
|
||||
<UContainer>
|
||||
<UMain>
|
||||
<UPage>
|
||||
<!-- <UPageError :error="error" /> -->
|
||||
</UPage>
|
||||
</UMain>
|
||||
</UContainer>
|
||||
<UError :error="error" />
|
||||
|
||||
<Footer />
|
||||
|
||||
<LazyUContentSearch :files="files" :navigation="navigation" :fuse="{ resultLimit: 42 }" />
|
||||
<ClientOnly>
|
||||
<LazyUContentSearch :files="files" :navigation="navigation" :fuse="{ resultLimit: 42 }" />
|
||||
</ClientOnly>
|
||||
</UApp>
|
||||
</template>
|
||||
|
||||
@@ -31,13 +31,12 @@ useSeoMeta({
|
||||
titleTemplate: '%s - Nuxt UI v3',
|
||||
title: page.value.navigation?.title || page.value.title,
|
||||
ogTitle: `${page.value.navigation?.title || page.value.title} - Nuxt UI v3`,
|
||||
description: page.value.description,
|
||||
ogDescription: page.value.description
|
||||
description: page.value.seo?.description || page.value.description,
|
||||
ogDescription: page.value.seo?.description || page.value.description
|
||||
})
|
||||
|
||||
defineOgImageComponent('Docs', {
|
||||
headline: headline.value,
|
||||
title: page.value.title
|
||||
headline: headline.value
|
||||
})
|
||||
|
||||
const communityLinks = computed(() => [{
|
||||
@@ -76,10 +75,24 @@ const communityLinks = computed(() => [{
|
||||
|
||||
<template>
|
||||
<UPage v-if="page">
|
||||
<UPageHeader :title="page.title" :links="page.links" :headline="headline">
|
||||
<UPageHeader :title="page.title" :headline="headline">
|
||||
<template #description>
|
||||
<MDC v-if="page.description" :value="page.description" unwrap="p" />
|
||||
</template>
|
||||
|
||||
<template #links>
|
||||
<UButton
|
||||
v-for="link in page.links"
|
||||
:key="link.label"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
v-bind="link"
|
||||
>
|
||||
<template v-if="link.avatar" #leading>
|
||||
<UAvatar v-bind="link.avatar" size="2xs" />
|
||||
</template>
|
||||
</UButton>
|
||||
</template>
|
||||
</UPageHeader>
|
||||
|
||||
<UPageBody>
|
||||
|
||||
@@ -3,7 +3,7 @@ const title = 'Roadmap'
|
||||
const description = 'Discover our Volta board for @nuxt/ui development status.'
|
||||
|
||||
useSeoMeta({
|
||||
titleTemplate: '%s - Nuxt UI',
|
||||
titleTemplate: '%s - Nuxt UI v3',
|
||||
title,
|
||||
ogTitle: 'Nuxt UI Roadmap',
|
||||
description
|
||||
@@ -20,7 +20,7 @@ const src = computed(() => `https://volta.net/embed/${token}?theme=${colorMode.v
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-[calc(100vh-var(--header-height)-var(--header-height)-1px)]">
|
||||
<div class="h-[calc(100vh-var(--ui-header-height)-var(--ui-header-height)-48px-1px)]">
|
||||
<ClientOnly>
|
||||
<iframe :src="src" width="100%" height="100%" />
|
||||
</ClientOnly>
|
||||
|
||||
@@ -138,3 +138,33 @@ export default defineNuxtConfig({
|
||||
::note
|
||||
This option adds the `transition-colors` class on components with hover or active states.
|
||||
::
|
||||
|
||||
## Continuous Releases
|
||||
|
||||
Nuxt UI v3 uses [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new) for continuous preview releases, providing developers with instant access to the latest features and bug fixes without waiting for official releases.
|
||||
|
||||
Preview releases are automatically generated for every commit to the `v3` branch and pull requests targeting the `v3` branch. To use it into your project, use the installation command below by replacing `5385f84` with any commit hash or pull request number.
|
||||
|
||||
::code-group
|
||||
|
||||
```bash [pnpm]
|
||||
pnpm add https://pkg.pr.new/@nuxt/ui@5385f84
|
||||
```
|
||||
|
||||
```bash [yarn]
|
||||
yarn add https://pkg.pr.new/@nuxt/ui@5385f84
|
||||
```
|
||||
|
||||
```bash [npm]
|
||||
npm install https://pkg.pr.new/@nuxt/ui@5385f84
|
||||
```
|
||||
|
||||
```bash [bun]
|
||||
bun add https://pkg.pr.new/@nuxt/ui@5385f84
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
::note
|
||||
**pkg.pr.new** will automatically comment on PRs with the installation URL, making it easy to test changes.
|
||||
::
|
||||
|
||||
@@ -110,7 +110,7 @@ export default defineAppConfig({
|
||||
```
|
||||
|
||||
::note
|
||||
Try the :prose-icon{name="i-heroicons-swatch" class="text-[--ui-primary]"} theme picker in the header above to change `primary` and `neutral` colors.
|
||||
Try the :prose-icon{name="i-heroicons-swatch" class="text-[var(--ui-primary)]"} theme picker in the header above to change `primary` and `neutral` colors.
|
||||
::
|
||||
|
||||
These colors are used to style the components but also to generate the `color` variants:
|
||||
@@ -211,7 +211,7 @@ Nuxt UI automatically creates a CSS variable for each color alias you define whi
|
||||
::
|
||||
|
||||
::note
|
||||
You can use these variables in classes like `text-[--ui-primary]`, it will automatically adapt to the current color scheme.
|
||||
You can use these variables in classes like `text-[var(--ui-primary)]`, it will automatically adapt to the current color scheme.
|
||||
::
|
||||
|
||||
::tip
|
||||
@@ -308,7 +308,7 @@ Nuxt UI automatically applies a text and background color on the `<body>` elemen
|
||||
|
||||
```css
|
||||
body {
|
||||
@apply antialiased font-sans text-[--ui-text] bg-[--ui-bg];
|
||||
@apply antialiased text-[var(--ui-text)] bg-[var(--ui-bg)];
|
||||
}
|
||||
```
|
||||
::
|
||||
@@ -345,7 +345,7 @@ Nuxt UI uses a global `--ui-radius` CSS variable for consistent border rounding.
|
||||
```
|
||||
|
||||
::note
|
||||
Try the :prose-icon{name="i-heroicons-swatch" class="text-[--ui-primary]"} theme picker in the header above to change the base radius value.
|
||||
Try the :prose-icon{name="i-heroicons-swatch" class="text-[var(--ui-primary)]"} theme picker in the header above to change the base radius value.
|
||||
::
|
||||
|
||||
::tip
|
||||
@@ -376,7 +376,7 @@ Components in Nuxt UI can have multiple `slots`, each representing a distinct HT
|
||||
```ts [src/theme/card.ts]
|
||||
export default {
|
||||
slots: {
|
||||
root: 'bg-[--ui-bg] ring ring-[--ui-border] divide-y divide-[--ui-border] rounded-[calc(var(--ui-radius)*2)] shadow',
|
||||
root: 'bg-[var(--ui-bg)] ring ring-[var(--ui-border)] divide-y divide-[var(--ui-border)] rounded-[calc(var(--ui-radius)*2)] shadow',
|
||||
header: 'p-4 sm:px-6',
|
||||
body: 'p-4 sm:p-6',
|
||||
footer: 'p-4 sm:px-6'
|
||||
@@ -435,7 +435,7 @@ Nuxt UI components use `variants` to change the `slots` styles based on props. H
|
||||
```ts [src/theme/avatar.ts]
|
||||
export default {
|
||||
slots: {
|
||||
root: 'inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[--ui-bg-elevated]',
|
||||
root: 'inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)]',
|
||||
image: 'h-full w-full rounded-[inherit] object-cover'
|
||||
},
|
||||
variants: {
|
||||
@@ -464,7 +464,7 @@ This way, the `size` prop will apply the corresponding styles to the `root` slot
|
||||
ignore:
|
||||
- src
|
||||
props:
|
||||
src: 'https://github.com/benjamincanac.png'
|
||||
src: 'https://github.com/nuxt.png'
|
||||
size: lg
|
||||
---
|
||||
::
|
||||
|
||||
@@ -123,12 +123,16 @@ const items = [{
|
||||
label: 'Save',
|
||||
icon: 'i-heroicons-document-arrow-down',
|
||||
kbds: ['meta', 'S'],
|
||||
select: () => save()
|
||||
onSelect() {
|
||||
save()
|
||||
}
|
||||
}, {
|
||||
label: 'Copy',
|
||||
icon: 'i-heroicons-document-duplicate',
|
||||
kbds: ['meta', 'C'],
|
||||
select: () => copy()
|
||||
onSelect() {
|
||||
copy()
|
||||
}
|
||||
}]
|
||||
|
||||
defineShortcuts(extractShortcuts(items))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user