Compare commits

...

73 Commits

Author SHA1 Message Date
Benjamin Canac
68f0269046 chore(release): v3.0.0-alpha.7 2024-10-23 21:27:57 +02:00
Benjamin Canac
47d9955ed9 chore(renovate): ignore resolutions 2024-10-23 21:22:21 +02:00
renovate[bot]
6386a4d99a chore(deps): update all non-major dependencies (v3) (#2418)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-23 21:17:55 +02:00
Benjamin Canac
7687ac16fd docs(table): update 2024-10-23 17:51:28 +02:00
Benjamin Canac
90a775bab9 docs(table): update 2024-10-23 17:49:17 +02:00
renovate[bot]
efeb3f9cfa chore(deps): update tailwindcss to v4.0.0-alpha.29 (v3) (#2440)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-23 17:44:59 +02:00
Benjamin Canac
b54950e3ed feat(Table): implement component (#2364) 2024-10-23 17:32:30 +02:00
Sandro Circi
34bddd45be feat(NavigationMenu): handle children on vertical orientation (#2384)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-10-22 22:52:38 +02:00
Benjamin Canac
69d7b57825 docs(error): update loading indicator color 2024-10-22 12:57:36 +02:00
Benjamin Canac
92873e05ba docs(app): update error 2024-10-22 12:51:42 +02:00
Benjamin Canac
7870288367 docs(app): improve meta 2024-10-22 12:51:32 +02:00
Benjamin Canac
9d3d5db376 docs(deps): update @nuxt/ui-pro 2024-10-22 12:51:18 +02:00
renovate[bot]
090fe16cff chore(deps): update devdependency @nuxt/test-utils to ^3.14.4 (v3) (#2423)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-21 18:21:44 +02:00
renovate[bot]
44ebb35953 chore(deps): lock file maintenance (v3) (#2428)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-21 18:15:17 +02:00
Benjamin Canac
5000a4e0d5 playground(select-menu): add missing placeholder 2024-10-19 18:27:07 +02:00
rizkyyy
5385944359 feat(Form): add superstruct validation (#2363)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
Co-authored-by: Romain Hamel <rom.hml@gmail.com>
2024-10-19 14:07:22 +02:00
Tomy Kho
7802aacf3f fix(InputMenu): emit focus event (#2386)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-10-18 16:09:04 +02:00
Benjamin Canac
f59844bb61 fix(Input/InputMenu/Select/SelectMenu): uniformize placeholder color 2024-10-18 15:48:08 +02:00
Benjamin Canac
9359603a0a docs(input): add examples 2024-10-18 15:42:27 +02:00
Benjamin Canac
d407c42be7 docs(deps): update @nuxt/ui-pro 2024-10-18 15:17:53 +02:00
renovate[bot]
8a06981df2 chore(deps): update tailwindcss to v4.0.0-alpha.28 (v3) (#2412)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-18 15:17:33 +02:00
Benjamin Canac
a68016ec5d fix(Slideover): set max height on top / bottom positions
Resolves nuxt/ui#2388
2024-10-17 22:44:56 +02:00
Benjamin Canac
973023a04e chore(css): move keyframes into separate file 2024-10-17 22:32:26 +02:00
renovate[bot]
f6789a156c chore(deps): update all non-major dependencies (v3) (#2395)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-17 22:15:17 +02:00
Benjamin Canac
61b232377b fix(AvatarGroup): wrong ring on big sizes 2024-10-17 22:14:29 +02:00
Benjamin Canac
eb1b30db40 playground(app): improve responsive navigation 2024-10-17 22:06:34 +02:00
Benjamin Canac
b975235c8b feat(ContextMenu/DropdownMenu): handle loading field in items 2024-10-17 22:03:32 +02:00
Benjamin Canac
81a59969f6 chore(ContextMenu/DropdownMenu): prevent useless extends 2024-10-17 21:47:23 +02:00
Benjamin Canac
dc8cd1e664 docs(components): ignore props in ComponentProps 2024-10-17 21:46:53 +02:00
Benjamin Canac
c63920d05b docs(context-menu/dropdown-menu): improve items properties 2024-10-17 21:25:50 +02:00
Benjamin Canac
37171b9327 chore(components): prevent useless extends on items 2024-10-17 21:25:17 +02:00
Benjamin Canac
49abad243c feat(CommandPalette): handle loading field in items 2024-10-17 21:13:11 +02:00
Benjamin Canac
c1294f6505 chore(components): prioritize icon over avatar in items 2024-10-17 21:06:10 +02:00
Benjamin Canac
53a3796d1b feat(Input/InputMenu/Select/SelectMenu): handle avatar prop 2024-10-17 18:21:12 +02:00
Benjamin Canac
df2013ca92 fix(Button): invalid hover on link variant 2024-10-17 17:41:30 +02:00
Benjamin Canac
0666884b6f docs: use nuxt.png in avatars 2024-10-17 17:10:39 +02:00
Benjamin Canac
a54c3e49fe feat(Button): handle avatar prop 2024-10-17 17:09:59 +02:00
Benjamin Canac
716ed10068 test: consistent avatar urls 2024-10-17 16:33:50 +02:00
Benjamin Canac
e137577a72 docs: consistent avatar urls 2024-10-17 16:31:51 +02:00
Benjamin Canac
0f9349f920 playground: consistent avatar urls 2024-10-17 16:31:41 +02:00
Benjamin Canac
e6143e8600 docs(deps): add wrangler as devDependencies 2024-10-15 18:34:03 +02:00
renovate[bot]
b9380c15ab chore(deps): update tailwindcss to v4.0.0-alpha.27 (v3) (#2263)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-15 18:03:07 +02:00
renovate[bot]
0b7f171268 chore(deps): update all non-major dependencies (v3) (#2382)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-10-15 17:29:53 +02:00
Benjamin Canac
490fb1377b docs(deps): use @nuxt/ui-pro preview release 2024-10-15 17:14:56 +02:00
Benjamin Canac
8ef6e712ac feat(ContextMenu/DropdownMenu): handle checkbox items type
Resolves #2144
2024-10-15 17:14:56 +02:00
Benjamin Canac
0759e29c22 docs(installation): add Continuous Releases section 2024-10-15 17:03:44 +02:00
Benjamin Canac
5385f84e0a chore(github): split docs typecheck 2024-10-15 16:33:17 +02:00
Benjamin Canac
46bd1cb002 docs(carousel): use useTemplateRef 2024-10-15 15:21:53 +02:00
Benjamin Canac
8258cd3829 docs(form): use useTemplateRef 2024-10-15 15:21:42 +02:00
Benjamin Canac
16b48efa96 docs(ThemePicker): remove select event 2024-10-15 15:13:43 +02:00
Benjamin Canac
b39c4d127e fix(components)!: rename select to onSelect on items 2024-10-15 15:13:43 +02:00
renovate[bot]
6af276ef38 chore(deps): update devdependency @release-it/conventional-changelog to v9 (v3) (#2368)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-14 18:31:37 +02:00
renovate[bot]
67fe33f820 chore(deps): update all non-major dependencies (v3) (#2352)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-14 17:55:59 +02:00
renovate[bot]
9e8b9dcc62 chore(deps): lock file maintenance (v3) (#2376)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-14 17:23:43 +02:00
Benjamin Canac
e9affb6f5b docs(command-palette): update 2024-10-14 14:48:14 +02:00
Benjamin Canac
6e9f6a8ef4 docs(accordion): add alert about multiple and v-model
Resolves #2372
2024-10-14 14:47:40 +02:00
Sandro Circi
dcce571cda fix(module): stop using tailwind's shorthand arbitrary variable syntax (#2366)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
2024-10-14 10:42:26 +02:00
Benjamin Canac
ea07dffdd5 chore(github): update issue templates 2024-10-12 19:13:51 +02:00
Benjamin Canac
acfc6cef2d feat(Accordion/Breadcrumb/CommandPalette/ContextMenu/DropdownMenu/NavigationMenu/Tabs): add labelKey prop 2024-10-11 18:30:21 +02:00
Benjamin Canac
f6f9823b15 feat(InputMenu/RadioGroup/Select/SelectMenu): handle labelKey and use get to support dot notation 2024-10-11 15:31:47 +02:00
Benjamin Canac
296ae456c9 chore(components): move utils imports before components 2024-10-11 14:15:44 +02:00
Benjamin Canac
f6631ff7bc fix(Checkbox): indeterminate prop not working 2024-10-11 14:13:46 +02:00
Benjamin Canac
bcfa4b74a9 fix(Drawer/Modal/Slideover): no need for z-index since its isolated
Resolves nuxt/ui#2347
2024-10-11 00:08:17 +02:00
Benjamin Canac
1a7af6d182 test(SelectMenu): update snapshots 2024-10-11 00:07:39 +02:00
Benjamin Canac
558871a46a docs(Header): fix logo focus 2024-10-10 21:11:23 +02:00
Benjamin Canac
c8c17490ab docs(roadmap): fix height 2024-10-10 21:07:50 +02:00
Benjamin Canac
9e03da41b3 fix(css): font-sans already applied on <html> 2024-10-10 17:13:46 +02:00
Benjamin Canac
a2bad2eee2 fix(css): make @theme default 2024-10-10 17:13:05 +02:00
Benjamin Canac
7c21ddefa8 fix(InputMenu/SelectMenu): escape regexp before search 2024-10-10 16:12:22 +02:00
Benjamin Canac
0f9ac8733e fix(InputMenu/SelectMenu): improve displayed value
Resolves nuxt/ui#2353
2024-10-10 16:01:44 +02:00
Benjamin Canac
365bc0fc9a docs(select/select-menu): apply width class on examples
Resolves #2297
2024-10-10 15:34:33 +02:00
Benjamin Canac
c34a805e5f docs: improve hard-coded rounded 2024-10-09 18:24:50 +02:00
Benjamin Canac
b4ffcedd2e docs(deps): update @nuxt/ui-pro 2024-10-09 17:05:35 +02:00
292 changed files with 13253 additions and 7592 deletions

65
.github/ISSUE_TEMPLATE/bug-v3.yml vendored Normal file
View 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

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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)

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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">

View File

@@ -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>

View File

@@ -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>

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -28,6 +28,14 @@ function stripCompoundVariants(component?: any) {
}
}
if (compoundVariant.loadingColor) {
if (!['primary', 'neutral'].includes(compoundVariant.loadingColor)) {
strippedCompoundVariants.value = true
return false
}
}
return true
})
}

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -1,5 +1,5 @@
<template>
<UCollapsible class="w-48">
<UCollapsible class="flex flex-col gap-2 w-48">
<UButton
class="group"
label="Open"

View File

@@ -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"

View File

@@ -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 }">

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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' },

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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'

View File

@@ -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>

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -32,7 +32,7 @@ const groups = computed(() => [{
:loading="status === 'pending'"
:groups="groups"
placeholder="Search users..."
class="h-96"
class="h-80"
/>
</template>
</UModal>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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'
}"
>

View File

@@ -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'
}"
>

View File

@@ -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'
}"
>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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.
::

View File

@@ -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
---
::

View File

@@ -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