Compare commits

...

57 Commits

Author SHA1 Message Date
Benjamin Canac
15f9db9420 chore(release): 2.0.1 2023-05-11 16:26:11 +02:00
Benjamin Canac
aacb7e9841 fix(CommandPalette): put back searchable on v-show to input ref always exists 2023-05-11 15:45:33 +02:00
Benjamin Canac
3ded73194d docs: improve command palettes 2023-05-11 15:24:26 +02:00
Benjamin Canac
82adedf764 docs: improve search 2023-05-11 15:18:01 +02:00
Benjamin Canac
192bf4c375 fix(CommandPalette): expose input ref to template 2023-05-11 15:17:47 +02:00
Benjamin Canac
59fc14e93f chore(SelectMenu): add prop for trailingIcon and fix padding when selected 2023-05-11 14:28:58 +02:00
Benjamin Canac
0d83366427 chore(Select): add prop for trailingIcon 2023-05-11 14:21:30 +02:00
Benjamin Canac
cc65afafbd chore(CommandPaletteGroup): rename ui.group.command.selected.icon to ui.group.command.selectedIcon.base 2023-05-11 14:21:01 +02:00
Benjamin Canac
c7e0cb40e7 chore(CommandPaletteGroup): style <mark> with primary 2023-05-11 14:18:14 +02:00
Benjamin Canac
24434dc561 docs: default value in select menu basic example 2023-05-11 14:17:28 +02:00
Benjamin Canac
6c35ee9270 docs: typo in context-menu 2023-05-11 14:17:12 +02:00
Benjamin Canac
950b341696 docs: prevent aside to scroll up on page change 2023-05-11 14:17:01 +02:00
Benjamin Canac
24e7109959 chore(deps): bump 2023-05-10 18:35:24 +02:00
Benjamin Canac
be96824323 chore(SelectMenu): improve option.selected preset 2023-05-10 18:33:43 +02:00
Benjamin Canac
d5471f4d37 fix(Toggle): wrong icon-off positioning 2023-05-10 18:14:21 +02:00
Benjamin Canac
00b444b3eb chore: rename spacing to padding 2023-05-10 16:24:28 +02:00
Benjamin Canac
76a0d61a0f fix(colors): missing useNuxtApp import 2023-05-10 12:39:08 +02:00
Benjamin Canac
6d79548ee8 chore(Slideover): default side to right 2023-05-10 12:27:20 +02:00
Benjamin Canac
f48ead6faf fix(docs): sticky search button z-index 2023-05-10 11:59:30 +02:00
Benjamin Canac
fd4c80acd4 fix(Avatar): gray missing for chipColor 2023-05-10 11:59:16 +02:00
Benjamin Canac
3df917ae70 chore(deps): fix vue-tsc version to 1.6.3 2023-05-10 00:22:10 +02:00
Benjamin Canac
19a34c44da chore(plugins): move hexToRgb fn to utils 2023-05-10 00:19:27 +02:00
Benjamin Canac
365c843fc0 chore(SelectMenu): put back required input 2023-05-10 00:18:14 +02:00
Benjamin Canac
cd430a4cad fix(Icon): missing import 2023-05-10 00:17:20 +02:00
Benjamin Canac
32ada0b28b chore(deps): bump @nuxtjs/tailwindcss 2023-05-09 18:55:28 +02:00
Benjamin Canac
034a95d3c9 fix(VerticalNavigation): improve focus 2023-05-09 18:55:15 +02:00
Benjamin Canac
530d8a8c27 chore(Dropdown): improve preset 2023-05-09 17:58:07 +02:00
Benjamin Canac
939efba47c fix(app.config): remove old u- classes 2023-05-09 16:26:12 +02:00
Benjamin Canac
c43c212ae1 chore(Toggle): shrink size and move focus to focus-visible 2023-05-09 16:25:09 +02:00
Benjamin Canac
0404c871fb chore(Radio/Checkbox): change focus to focus-visible 2023-05-09 16:24:36 +02:00
Benjamin Canac
ebf5fd6aeb fix(Avatar): shrink chip ring 2023-05-09 16:23:51 +02:00
Benjamin Canac
d5d250b8cf chore(Badge): default size to sm and add lg 2023-05-09 14:30:43 +02:00
Benjamin Canac
410d2351d6 chore(Avatar): default size to sm 2023-05-09 14:30:43 +02:00
Benjamin Canac
949a476125 docs: add fallback on colorMode button 2023-05-09 14:30:43 +02:00
Benjamin Canac
4586eed90c chore(Kbd): new component 2023-05-09 14:30:43 +02:00
Benjamin Canac
b21c55f5c4 chore(Dropdown): move resolve class logic into template 2023-05-09 14:30:43 +02:00
Benjamin Canac
fc11612a49 chore(SelectMenu): move resolve class logic into template 2023-05-09 14:30:05 +02:00
Benjamin Canac
6de57aa1a0 chore(deps): bump 2023-05-09 11:50:28 +02:00
Benjamin Canac
6355b16156 chore(Button): add flex-shrink-0 by default 2023-05-05 18:17:43 +02:00
Benjamin Canac
26fc923ea4 chore(Input): add placeholder color to none appearance 2023-05-05 18:17:32 +02:00
Benjamin Canac
28ee9179f5 fix(VerticalNavigation): improve stacking context 2023-05-05 18:17:13 +02:00
Benjamin Canac
4665563e6f fix(CommandPalette): wrong type usage 2023-05-05 17:19:39 +02:00
Benjamin Canac
f221b890a9 chore(Card): update ring and divide colors 2023-05-05 17:17:46 +02:00
Benjamin Canac
d1d8ab3c64 fix(Button): variant validator takes color into account 2023-05-05 14:38:03 +02:00
Benjamin Canac
5b8ab168ba chore(app.config): remove commented button gray variant 2023-05-05 12:16:18 +02:00
Benjamin Canac
767a2bf3fc chore(app.config): improve modal and slideover overlay background 2023-05-05 12:15:59 +02:00
Benjamin Canac
0c69385771 fix: prefix imported components 2023-05-05 12:15:20 +02:00
Daniel Roe
97b1a85ea1 fix: revert back to runtime app for hmr (#153) 2023-05-04 18:18:36 +02:00
Benjamin Canac
9ce43ac68b fix(Notifications): missing computed from vue 2023-05-04 17:59:10 +02:00
Benjamin Canac
fa05653f23 fix(Select): move types from template 2023-05-04 17:51:23 +02:00
Benjamin Canac
626409e101 fix: put back app.config for hmr 2023-05-04 17:51:08 +02:00
Daniel Roe
f5c0030a19 fix: remove augmentation of app (#152) 2023-05-04 17:32:25 +02:00
Daniel Roe
11e00a10e4 fix: update to fix type issues (#151) 2023-05-04 17:24:22 +02:00
Benjamin Canac
b55a7c58f6 docs: improve installation description 2023-05-04 16:02:26 +02:00
Benjamin Canac
8c8bc0b751 docs: hide unfinished pages 2023-05-04 16:00:20 +02:00
Benjamin Canac
a076cae4bf fix(module): remove .ts ext from app.config 2023-05-04 15:38:32 +02:00
Benjamin Canac
5facfee76c Revert "chore(package): volta pin node 18"
This reverts commit a38ef00fb8.
2023-05-04 14:59:45 +02:00
57 changed files with 669 additions and 588 deletions

View File

@@ -2,6 +2,33 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [2.0.1](https://github.com/nuxtlabs/ui/compare/v2.0.0...v2.0.1) (2023-05-11)
### Bug Fixes
* **app.config:** remove old `u-` classes ([939efba](https://github.com/nuxtlabs/ui/commit/939efba47ceb660e5448a3ea42f2acd71b9837ee))
* **Avatar:** `gray` missing for `chipColor` ([fd4c80a](https://github.com/nuxtlabs/ui/commit/fd4c80acd4c70c7d378ebf780cd843115d8f434d))
* **Avatar:** shrink chip ring ([ebf5fd6](https://github.com/nuxtlabs/ui/commit/ebf5fd6aeb2a5363e80457cf8245fbab5fbc17ca))
* **Button:** `variant` validator takes color into account ([d1d8ab3](https://github.com/nuxtlabs/ui/commit/d1d8ab3c647d50f37832d1ae531550944d5aa8e3))
* **colors:** missing `useNuxtApp` import ([76a0d61](https://github.com/nuxtlabs/ui/commit/76a0d61a0f7b3936b0eceff16e17bc6540fb946c))
* **CommandPalette:** expose input ref to template ([192bf4c](https://github.com/nuxtlabs/ui/commit/192bf4c375293b16d952b94cc098a0260f47996a))
* **CommandPalette:** put back searchable on `v-show` to input ref always exists ([aacb7e9](https://github.com/nuxtlabs/ui/commit/aacb7e98412d2973c6fc61d9cb3b6da9bd433eb0))
* **CommandPalette:** wrong type usage ([4665563](https://github.com/nuxtlabs/ui/commit/4665563e6f9c4054cb1c859991369fe2cc844047))
* **docs:** sticky search button `z-index` ([f48ead6](https://github.com/nuxtlabs/ui/commit/f48ead6faf6fd14deeff84ca7b25d6bb7fae6f12))
* **Icon:** missing import ([cd430a4](https://github.com/nuxtlabs/ui/commit/cd430a4cad5143c5bd45c003086091f769e4f015))
* **module:** remove `.ts` ext from app.config ([a076cae](https://github.com/nuxtlabs/ui/commit/a076cae4bfa387e1fd9800741b10702896c21ad2))
* **Notifications:** missing `computed` from vue ([9ce43ac](https://github.com/nuxtlabs/ui/commit/9ce43ac68bcef3fb7fff8a9e317ad6d4a5ac2cb9))
* prefix imported components ([0c69385](https://github.com/nuxtlabs/ui/commit/0c69385771ff1815cdcbff812962056da381a541))
* put back app.config for hmr ([626409e](https://github.com/nuxtlabs/ui/commit/626409e1014ddcacaf6ee155830bd9862b335058))
* remove augmentation of app ([#152](https://github.com/nuxtlabs/ui/issues/152)) ([f5c0030](https://github.com/nuxtlabs/ui/commit/f5c0030a198579e5929fd517b80e2e20c9bac769))
* revert back to runtime app for hmr ([#153](https://github.com/nuxtlabs/ui/issues/153)) ([97b1a85](https://github.com/nuxtlabs/ui/commit/97b1a85ea12499289866a6baf15661c1f15279ce))
* **Select:** move types from template ([fa05653](https://github.com/nuxtlabs/ui/commit/fa05653f23c4e9b1732eb4b9cd5e034f9bdca272))
* **Toggle:** wrong `icon-off` positioning ([d5471f4](https://github.com/nuxtlabs/ui/commit/d5471f4d371b72df0ca5fac36e698066aca3864e))
* update to fix type issues ([#151](https://github.com/nuxtlabs/ui/issues/151)) ([11e00a1](https://github.com/nuxtlabs/ui/commit/11e00a10e4781881e293e5fcd382331008c15346))
* **VerticalNavigation:** improve focus ([034a95d](https://github.com/nuxtlabs/ui/commit/034a95d3c92eee9a54bd266e02d7446f7792d051))
* **VerticalNavigation:** improve stacking context ([28ee917](https://github.com/nuxtlabs/ui/commit/28ee9179f5fbc006a47719ee632adf54f0e0ec4d))
## [2.0.0](https://github.com/nuxtlabs/ui/compare/v1.2.11...v2.0.0) (2023-05-04)

View File

@@ -3,7 +3,21 @@
<Header />
<UContainer>
<NuxtPage />
<div class="relative grid lg:grid-cols-10 lg:gap-8">
<DocsAside class="lg:col-span-2" />
<div class="relative lg:col-span-6 pt-8 pb-16">
<DocsPageHeader />
<NuxtPage />
<hr class="border-gray-200 dark:border-gray-800 my-12">
<DocsPrevNext />
</div>
<DocsToc class="lg:col-span-2 order-first lg:order-last" />
</div>
</UContainer>
<DocsSearch />

View File

@@ -1,48 +1,38 @@
import type { RouterConfig } from '@nuxt/schema'
// https://router.vuejs.org/api/interfaces/routeroptions.html
export default <RouterConfig> {
scrollBehavior (to, _form, savedPosition) {
if (history.state.stop) { return }
function findHashPosition (hash): { el: any, behavior: ScrollBehavior, top: number } {
const el = document.querySelector(hash)
// vue-router does not incorporate scroll-margin-top on its own.
if (el) {
const top = parseFloat(getComputedStyle(el).scrollMarginTop)
return {
el: hash,
behavior: 'smooth',
top
}
}
}
// https://router.vuejs.org/api/#routeroptions
export default <RouterConfig>{
scrollBehavior (to, from, savedPosition) {
const nuxtApp = useNuxtApp()
// If history back
if (savedPosition) {
// Handle Suspense resolution
return new Promise((resolve) => {
nuxtApp.hooks.hookOnce('page:finish', () => {
setTimeout(() => resolve(savedPosition), 50)
})
})
if (history.state.smooth) {
return {
el: history.state.smooth,
behavior: 'smooth'
}
}
// Scroll to heading on click
if (to.hash) {
return new Promise((resolve) => {
if (to.path === from.path) {
setTimeout(() => resolve(findHashPosition(to.hash)), 50)
} else {
nuxtApp.hooks.hookOnce('page:finish', () => {
setTimeout(() => resolve(findHashPosition(to.hash)), 50)
})
}
})
const el = document.querySelector(to.hash) as any
if (!el) { return }
const { marginTop } = getComputedStyle(el)
const marginTopValue = parseInt(marginTop)
const offset = (document.querySelector(to.hash) as any).offsetTop - marginTopValue
return {
top: offset,
behavior: 'smooth'
}
}
// Scroll to top of window
return { top: 0 }
if (savedPosition) {
return savedPosition
} else {
return { top: 0 }
}
}
}

View File

@@ -31,7 +31,12 @@
aria-label="Theme"
@click="isDark = !isDark"
/>
<template #fallback>
<div class="w-8 h-8" />
</template>
</ClientOnly>
<UButton
to="https://github.com/nuxtlabs/ui"
target="_blank"

View File

@@ -20,7 +20,8 @@ const selected = ref([people[3]])
v-model="selected"
multiple
nullable
:autoselect="false"
:groups="[{ key: 'people', commands: people }]"
:fuse="{ resultLimit: 6 }"
:fuse="{ resultLimit: 6, fuseOptions: { threshold: 0.1 } }"
/>
</template>

View File

@@ -10,10 +10,10 @@ const users = [
]
const actions = [
{ id: 'new-file', label: 'Add new file', icon: 'i-heroicons-document-plus', click: () => alert('New file') },
{ id: 'new-folder', label: 'Add new folder', icon: 'i-heroicons-folder-plus', click: () => alert('New folder') },
{ id: 'hashtag', label: 'Add hashtag', icon: 'i-heroicons-hashtag', click: () => alert('Add hashtag') },
{ id: 'label', label: 'Add label', icon: 'i-heroicons-tag', click: () => alert('Add label') }
{ id: 'new-file', label: 'Add new file', icon: 'i-heroicons-document-plus', click: () => alert('New file'), shortcuts: ['⌘', 'N'] },
{ id: 'new-folder', label: 'Add new folder', icon: 'i-heroicons-folder-plus', click: () => alert('New folder'), shortcuts: ['⌘', 'F'] },
{ id: 'hashtag', label: 'Add hashtag', icon: 'i-heroicons-hashtag', click: () => alert('Add hashtag'), shortcuts: ['⌘', 'H'] },
{ id: 'label', label: 'Add label', icon: 'i-heroicons-tag', click: () => alert('Add label'), shortcuts: ['⌘', 'L'] }
]
const groups = computed(() => commandPaletteRef.value?.query
@@ -42,5 +42,5 @@ function onSelect (option) {
</script>
<template>
<UCommandPalette ref="commandPaletteRef" :groups="groups" @update:model-value="onSelect" />
<UCommandPalette ref="commandPaletteRef" :groups="groups" :autoselect="false" @update:model-value="onSelect" />
</template>

View File

@@ -25,7 +25,7 @@ function openContextMenu () {
Right click here
</Placeholder>
<UContextMenu v-model="isOpen" :virtual-element="virtualElement" width-class="w-48">
<UContextMenu v-model="isOpen" :virtual-element="virtualElement">
<div class="p-4">
Menu
</div>

View File

@@ -7,10 +7,12 @@ const items = [
}
}], [{
label: 'Edit',
icon: 'i-heroicons-pencil-square-20-solid'
icon: 'i-heroicons-pencil-square-20-solid',
shortcuts: ['E']
}, {
label: 'Duplicate',
icon: 'i-heroicons-document-duplicate-20-solid'
icon: 'i-heroicons-document-duplicate-20-solid',
shortcuts: ['D']
}], [{
label: 'Archive',
icon: 'i-heroicons-archive-box-20-solid'
@@ -19,7 +21,8 @@ const items = [
icon: 'i-heroicons-arrow-right-circle-20-solid'
}], [{
label: 'Delete',
icon: 'i-heroicons-trash-20-solid'
icon: 'i-heroicons-trash-20-solid',
shortcuts: ['⌘', 'D']
}]
]
</script>

View File

@@ -0,0 +1,10 @@
<script setup>
const { metaSymbol } = useShortcuts()
</script>
<template>
<div class="flex items-center gap-0.5">
<UKbd>{{ metaSymbol }}</UKbd>
<UKbd>U</UKbd>
</div>
</template>

View File

@@ -1,7 +1,7 @@
<script setup>
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
const selected = ref()
const selected = ref(people[0])
</script>
<template>

View File

@@ -1,5 +1,5 @@
<template>
<UTooltip text="Tooltip">
<UButton color="gray" label="Button" />
<UTooltip text="Tooltip example" :shortcuts="['⌘', 'O']">
<UButton color="gray" label="Hover me" />
</UTooltip>
</template>

View File

@@ -24,8 +24,14 @@ const ui = {
wrapper: 'flex flex-col flex-1 min-h-0 bg-gray-50 dark:bg-gray-800',
input: {
wrapper: 'relative flex items-center mx-3 py-3',
base: 'w-full rounded border-2 border-primary-500 placeholder-gray-400 dark:placeholder-gray-500 focus:border-primary-500 focus:outline-none focus:ring-0 h-14 text-lg bg-white dark:bg-gray-900',
icon: 'pointer-events-none absolute left-3 h-6 w-6 text-primary-500 dark:text-primary-400'
base: 'w-full rounded border-2 border-primary-500 placeholder-gray-400 dark:placeholder-gray-500 focus:border-primary-500 focus:outline-none focus:ring-0 bg-white dark:bg-gray-900',
padding: 'px-4',
height: 'h-14',
size: 'text-lg',
icon: {
base: 'pointer-events-none absolute left-3 text-primary-500 dark:text-primary-400',
size: 'h-6 w-6'
}
},
group: {
wrapper: 'p-3 relative',
@@ -60,6 +66,7 @@ const ui = {
:ui="ui"
:close="close"
:empty="empty"
:autoselect="false"
command-attribute="title"
:fuse="{
fuseOptions: { keys: ['title', 'category'] },

View File

@@ -53,5 +53,12 @@ const ui = {
</script>
<template>
<UCommandPalette ref="commandPaletteRef" :groups="groups" icon="" :ui="ui" placeholder="Search for apps and commands" />
<UCommandPalette
ref="commandPaletteRef"
:groups="groups"
icon=""
:ui="ui"
:autoselect="false"
placeholder="Search for apps and commands"
/>
</template>

View File

@@ -1,9 +1,7 @@
<template>
<aside
class="hidden pb-8 overflow-y-auto lg:block lg:self-start lg:top-16 lg:max-h-[calc(100vh-64px)] lg:sticky lg:pr-8 lg:pl-[2px]"
>
<aside class="hidden pb-8 overflow-y-auto lg:block lg:self-start lg:top-16 lg:max-h-[calc(100vh-64px)] lg:sticky lg:pr-8 lg:pl-[2px]">
<div class="relative">
<div class="sticky top-0 pointer-events-none">
<div class="sticky top-0 pointer-events-none z-[1]">
<div class="h-8 bg-white dark:bg-gray-900" />
<div class="bg-white dark:bg-gray-900 relative pointer-events-auto">
<UButton
@@ -14,9 +12,9 @@
>
Search
<div class="hidden lg:flex items-center gap-1 ml-auto -my-1">
<Shortcut value="meta" />
<Shortcut value="K" />
<div class="hidden lg:flex items-center gap-0.5 ml-auto -my-1">
<UKbd>{{ metaSymbol }}</UKbd>
<UKbd>K</UKbd>
</div>
</UButton>
</div>
@@ -30,4 +28,5 @@
<script setup lang="ts">
const { isSearchModalOpen } = useDocs()
const { metaSymbol } = useShortcuts()
</script>

View File

@@ -10,7 +10,7 @@
class="mt-1"
:ui="{
wrapper: 'border-l border-gray-200 dark:border-gray-800 space-y-2',
spacing: 'pl-4',
padding: 'pl-4',
base: 'group text-sm block border-l -ml-px lg:leading-6',
active: 'text-primary-500 dark:text-primary-400 border-current font-semibold',
inactive: 'border-transparent hover:border-gray-400 dark:hover:border-gray-500 text-gray-700 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300'

View File

@@ -2,7 +2,7 @@
<UModal
v-model="isSearchModalOpen"
:ui="{
spacing: 'sm:p-4',
padding: 'sm:p-4',
rounded: 'sm:rounded-lg',
width: 'sm:max-w-3xl',
height: 'h-screen sm:h-[28rem]'
@@ -13,7 +13,7 @@
:groups="groups"
command-attribute="title"
:fuse="{
fuseOptions: { ignoreLocation: true, includeMatches: true, minMatchCharLength: 2, threshold: 0, keys: ['title', 'description', 'children.children.value', 'children.children.children.value'] },
fuseOptions: { ignoreLocation: true, includeMatches: true, threshold: 0, keys: ['title', 'description', 'children.children.value', 'children.children.children.value'] },
resultLimit: 10
}"
@update:model-value="onSelect"

View File

@@ -1,6 +1,6 @@
---
title: 'nuxthq/ui'
description: 'Components library as a Nuxt3 module using TailwindCSS based on TailwindUI.'
description: 'Components library as a Nuxt module using TailwindCSS and HeadlessUI.'
navigation:
title: Installation
---

View File

@@ -1 +1,5 @@
---
navigation: false
---
## Overview

View File

@@ -1,3 +1,7 @@
---
navigation: false
---
## Overview
## Composables

View File

@@ -1,152 +0,0 @@
---
navigation: false
---
## Breaking Changes
Classes to invert dark mode like `u-text-gray-900` have been removed.
- Components now have a `ui` prop to override the entire preset instead of individual props
- Components prop `popperOptions` has been renamed to `popper`
- `Alert`, `AlertDialog`, `Tabs` and `Pills` components have been removed
### `Avatar`
- `wrapperClass`, `backgroundClass`, `placeholderClass` and `roundedClass` props have been removed in favor of `ui`
- `rounded` prop is now a class defaulting to `rounded-full` instead of a boolean prop, can be overriden through `ui.avatar.rounded`
- `chip` prop is now `chipVariant`
- `ui.avatar.size` and `ui.avatar.chip.size` `xxs` and `xxxs` have been renamed respectively to `2xs` and `3xs`
### `AvatarGroup`
- `ringClass` and `marginClass` props have been removed in favor of `ui`
- `group` prop has been removed in favor of slots
### `Badge`
- `baseClass` prop has been removed in favor of `ui`
- `rounded` prop is now a class defaulting to `rounded-md` instead of a boolean prop, can be overriden through `ui.badge.rounded`
- `color` prop has been added to change the color scheme of the badge
- `variant` prop is now the variant instead of the color
- `font-medium` has been moved from `ui.badge.base` to `ui.badge.font`
### `Button`
- `customClass` prop have been removed
- `baseClass`, `iconBaseClass` and `roundedClass` props have been removed in favor of `ui`
- `leadingIconClass` and `trailingIconClass` props have been removed
- `rounded` prop is now a class defaulting to `rounded-md` instead of a boolean prop, can be overriden through `ui.button.rounded`
- `color` prop has been added to change the color scheme of the badge
- `variant` prop is now the variant instead of the color
- `labelCompact` and `compact` props have been removed entirely alongside preset `ui.button.compact` and `ui.button.icon.leading.compactSpacing` and `ui.button.icon.trailing.compactSpacing`
- `padded` prop has been added to remove padding
- `ui.button.size.xxs` has been renamed to `ui.button.size.2xs`
- `ui.button.size.2xl` has been introduced
- `ui.button.gap` has been introduced to replace margins defined in `ui.button.icon.leading.spacing` and `ui.button.icon.trailing.spacing`
- `ui.button.icon.leading.spacing` and `ui.button.icon.trailing.spacing` that added negative margin to icons have been removed to keep consitency when surcharging a button through default slot (code has only been commented for now)
- `font-medium` has been moved from `ui.button.base` to `ui.button.font`
### `ButtonGroup`
- New component
### `Dropdown`
- `wrapperClass`, `containerClass`, `widthClass`, `backgroundClass`, `shadowClass`, `roundedClass`, `ringClass`, `divideClass`, `baseClass`, `transitionClass`, `groupClass`, `itemBaseClass`, `itemActiveClass`, `itemInactiveClass`, `itemDisabledClass`, `itemIconClass`, `itemAvatarClass` and `itemShortcutsClass` props have been removed in favor of `ui`
- preset has been updated to improve dark mode
### `Card`
- `baseClass`, `backgroundClass`, `borderColorClass`, `shadowClass`, `ringClass`, `roundedClass`, `bodyClass`, `bodyBackgroundClass`, `headerClass`, `headerBackgroundClass`, `footerClass`, `footerBackgroundClass` props have been removed in favor of `ui`
- `rounded` prop is now a class defaulting to `rounded-lg` instead of a boolean prop, can be overriden through `ui.avatar.rounded`
- `padded` prop has been removed, use `ui.rounded = 'sm:rounded-lg'` instead when false
- `ui.card.border` has been removed in favor of `ui.card.divide`
- `ui.card.header` & `ui.card.footer` are now `{ spacing: '', background: '' }`
### `Container`
- `constrainedClass` prop has been removed in favor of `ui`
- `ui.container.base` and `ui.container.spacing` have been added
- `padded` prop has been removed, use `ui.spacing = 'sm:px-6 lg:px-8'` instead when false
- `constrained` prop has been removed, use `ui.constrained = ''` instead when false
### `Input`
- `wrapperClass`, `baseClass`, `iconBaseClass` and `customClass` props have been removed in favor of `ui`
### `FormGroup`
- Renamed to `InputGroup`
- `wrapperClass`, `containerClass`, `labelClass`, `labelWrapperClass`, `descriptionClass`, `requiredClass` and `hintClass` props have been removed in favor of `ui`
### `Textarea`
- `wrapperClass`, `baseClass` and `customClass` props have been removed in favor of `ui`
- `resize` is now false by default
### `Select`
- `wrapperClass`, `baseClass`, `iconBaseClass` and `customClass` props have been removed in favor of `ui`
### `SelectCustom`
- Renamed to `SelectMenu`
- `placeholder` prop is now `null` by default
- `nullable` prop has been removed
- `textAttribute` has been renamed to `optionAttribute` and now defaults to `label`
- `wrapperClass`, `baseClass`, `iconBaseClass`, `customClass`, `listBaseClass`, `listContainerClass`, `listWidthClass`, `listInputClass`, `listTransitionClass`, `listOptionBaseClass`, `listOptionContainerClass`, `listOptionActiveClass`, `listOptionInactiveClass`, `listOptionSelectedClass`, `listOptionUnselectedClass`, `listOptionDisabledClass`, `listOptionEmptyClass`, `listOptionIcon`, `listOptionIconBaseClass`, `listOptionIconActiveClass`, `listOptionIconInactiveClass` and `listOptionIconSizeClass` props have been removed in favor of `ui`
- `ui.selectCustom.list` has been moved to the root of `ui.selectMenu`, the component now uses `ui.select` to render the default slot
### `Radio`
- `wrapperClass`, `baseClass`, `labelClass`, `requiredClass`, `helpClass` and `customClass` props have been removed in favor of `ui`
### `Checkbox`
- `wrapperClass`, `baseClass`, `labelClass`, `requiredClass`, `helpClass` and `customClass` props have been removed in favor of `ui`
### `Toggle`
- `baseClass`, `activeClass`, `inactiveClass`, `containerBaseClass`, `containerActiveClass`, `containerInactiveClass`, `iconBaseClass`, `iconActiveClass`, `iconInactiveClass`, `iconOnClass` and `iconOffClass` props have been removed in favor of `ui`
### `CommandPalette`
- `inputCloseIcon` and `emptyIcon` props have been removed in favor of `ui`
- `inputIcon` prop has been renamed to `icon`
- `inputPlaceholder` prop has been renamed to `placeholder`
- `options` prop has been renamed to `fuse` to follow the `popper` and `ui` props convention
### `Modal`
- `wrapperClass`, `innerClass`, `containerClass`, `baseClass`, `backgroundClass`, `overlayBackgroundClass`, `overlayTransitionClass`, `shadowClass`, `ringClass`, `roundedClass`, `widthClass` and `transitionClass` props have been removed in favor of `ui`
- `innerStyle` prop has been removed
- `#header` and `#footer` slots have been removed
### `Slideover`
- `wrapperClass`, `baseClass`, `backgroundClass`, `overlayBackgroundClass`, `overlayTransitionClass`, `widthClass`, `headerClass` and `transitionClass` props have been removed in favor of `ui`
- `#header` slot has been removed
### `Popover`
- `wrapperClass`, `containerClass`, `widthClass`, `baseClass`, `backgroundClass`, `shadowClass`, `roundedClass`, `ringClass` and `transitionClass` props have been removed in favor of `ui`
### `Tooltip`
- `wrapperClass`, `containerClass`, `baseClass`, `widthClass`, `backgroundClass`, `shadowClass`, `ringClass`, `roundedClass`, `shortcutsClass` and `transitionClass` props have been removed in favor of `ui`
### `ContextMenu`
- `wrapperClass`, `containerClass`, `widthClass`, `backgroundClass`, `shadowClass`, `roundedClass`, `ringClass`, `baseClass` and `transitionClass`
### `Notification`
- `backgroundClass`, `shadowClass`, `ringClass`, `roundedClass`, `transitionClass`, `customClass` and `iconBaseClass` props have been removed in favor of `ui`
- `type` prop has been removed
- `ui.notification.type` and `ui.notification.icon.color` have been removed
- `ui.notification.close.icon.name` has been moved to `ui.notification.default.closeIcon`
### `useToast`
- `addNotification` and `removeNotification` have been renamed to `add` and `remove`
- `success`, `info`, `warning` and `error` methods have been removed as `type` disappeared from `Notification`

View File

@@ -19,7 +19,7 @@ Use the `size` prop to change the size of the Avatar.
::component-card
---
props:
size: 'md'
size: 'sm'
baseProps:
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
alt: 'Avatar'
@@ -66,7 +66,7 @@ To stack avatars as a group, use the `AvatarGroup` component.
::component-card{slug="AvatarGroup"}
---
props:
size: 'md'
size: 'sm'
max: 2
ui:
size:

View File

@@ -44,7 +44,7 @@ Use the `size` prop to change the size of the Badge.
::component-card
---
props:
size: 'md'
size: 'sm'
---
Badge

View File

@@ -22,10 +22,12 @@ const items = [
}
}], [{
label: 'Edit',
icon: 'i-heroicons-pencil-square-20-solid'
icon: 'i-heroicons-pencil-square-20-solid',
shortcuts: ['E']
}, {
label: 'Duplicate',
icon: 'i-heroicons-document-duplicate-20-solid'
icon: 'i-heroicons-document-duplicate-20-solid',
shortcuts: ['D']
}], [{
label: 'Archive',
icon: 'i-heroicons-archive-box-20-solid'
@@ -34,7 +36,8 @@ const items = [
icon: 'i-heroicons-arrow-right-circle-20-solid'
}], [{
label: 'Delete',
icon: 'i-heroicons-trash-20-solid'
icon: 'i-heroicons-trash-20-solid',
shortcuts: ['⌘', 'D']
}]
]
</script>

View File

@@ -0,0 +1,48 @@
---
github: true
title: 'Keyboard Key'
navigation:
title: 'Kbd'
---
## Usage
::component-example
#default
:kbd-example
#code
```vue
<script setup>
const { metaSymbol } = useShortcuts()
</script>
<template>
<div class="flex items-center gap-0.5">
<UKbd>{{ metaSymbol }}</UKbd>
<UKbd>U</UKbd>
</div>
</template>
```
::
### Size
Use the `size` prop to change the size of the Kbd.
::component-card
---
props:
size: 'sm'
---
U
::
## Props
:component-props
## Preset
:component-preset

View File

@@ -16,7 +16,7 @@ headlessui:
<script setup>
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
const selected = ref()
const selected = ref(people[0])
</script>
<template>

View File

@@ -10,6 +10,21 @@ headlessui:
::component-card
::
### Icon
Use any icon from [Iconify](https://icones.js.org) by setting the `icon-on` and `icon-off` props by using this pattern: `i-{collection_name}-{icon_name}`.
::component-card
---
props:
iconOn: 'i-heroicons-check-20-solid'
iconOff: 'i-heroicons-x-mark-20-solid'
excludedProps:
- iconOn
- iconOff
---
::
## Props
:component-props

View File

@@ -42,7 +42,7 @@ const selected = ref([people[3]])
multiple
nullable
:groups="[{ key: 'people', commands: people }]"
:fuse="{ resultLimit: 6 }"
:fuse="{ resultLimit: 6, fuseOptions: { threshold: 0.1 } }"
/>
</template>
```
@@ -118,10 +118,10 @@ const users = [
]
const actions = [
{ id: 'new-file', label: 'Add new file', icon: 'i-heroicons-document-plus', click: () => alert('New file') },
{ id: 'new-folder', label: 'Add new folder', icon: 'i-heroicons-folder-plus', click: () => alert('New folder') },
{ id: 'hashtag', label: 'Add hashtag', icon: 'i-heroicons-hashtag', click: () => alert('Add hashtag') },
{ id: 'label', label: 'Add label', icon: 'i-heroicons-tag', click: () => alert('Add label') }
{ id: 'new-file', label: 'Add new file', icon: 'i-heroicons-document-plus', click: () => alert('New file'), shortcuts: ['⌘', 'N'] },
{ id: 'new-folder', label: 'Add new folder', icon: 'i-heroicons-folder-plus', click: () => alert('New folder'), shortcuts: ['⌘', 'F'] },
{ id: 'hashtag', label: 'Add hashtag', icon: 'i-heroicons-hashtag', click: () => alert('Add hashtag'), shortcuts: ['⌘', 'H'] },
{ id: 'label', label: 'Add label', icon: 'i-heroicons-tag', click: () => alert('Add label'), shortcuts: ['⌘', 'L'] }
]
const groups = computed(() => commandPaletteRef.value?.query

View File

@@ -10,8 +10,8 @@ github: true
#code
```vue
<template>
<UTooltip text="Tooltip">
<UButton color="gray" label="Button" />
<UTooltip text="Tooltip example" :shortcuts="['⌘', 'O']">
<UButton color="gray" label="Hover me" />
</UTooltip>
</template>
```

View File

@@ -32,7 +32,7 @@ function openContextMenu () {
<template>
<div @contextmenu.prevent="openContextMenu">
<UContextMenu v-model="isOpen" :virtual-element="virtualElement" width-class="w-48">
<UContextMenu v-model="isOpen" :virtual-element="virtualElement">
<!-- Content -->
</UContextMenu>
</div>

View File

@@ -1,19 +1,5 @@
<template>
<div class="relative grid lg:grid-cols-10 lg:gap-8">
<DocsAside class="lg:col-span-2" />
<div class="relative lg:col-span-6 pt-8 pb-16">
<DocsPageHeader />
<div class="prose prose-primary dark:prose-invert max-w-none">
<slot />
</div>
<hr class="border-gray-200 dark:border-gray-800 my-12">
<DocsPrevNext />
</div>
<DocsToc class="lg:col-span-2 order-first lg:order-last" />
<div class="prose prose-primary dark:prose-invert max-w-none">
<slot />
</div>
</template>

View File

@@ -1,6 +1,6 @@
{
"name": "@nuxthq/ui",
"version": "2.0.0",
"version": "2.0.1",
"repository": "https://github.com/nuxtlabs/ui",
"license": "MIT",
"exports": {
@@ -33,10 +33,10 @@
"@iconify-json/heroicons": "^1.1.10",
"@nuxt/kit": "^3.4.3",
"@nuxtjs/color-mode": "^3.2.0",
"@nuxtjs/tailwindcss": "^6.6.7",
"@nuxtjs/tailwindcss": "^6.6.8",
"@popperjs/core": "^2.11.7",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/forms": "^0.0.0-insiders.615a228",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.9",
"@vueuse/core": "^10.1.2",
"@vueuse/integrations": "^10.1.2",
@@ -47,23 +47,20 @@
"tailwindcss": "^3.3.2"
},
"devDependencies": {
"@iconify-json/simple-icons": "^1.1.51",
"@iconify-json/simple-icons": "^1.1.52",
"@nuxt/content": "^2.6.0",
"@nuxt/module-builder": "^0.3.1",
"@nuxtjs/eslint-config-typescript": "^12.0.0",
"@nuxtjs/plausible": "^0.2.0",
"@nuxtjs/plausible": "^0.2.1",
"@types/lodash-es": "^4.17.7",
"@types/node": "^18.16.3",
"@types/node": "^20.1.2",
"@vueuse/nuxt": "^10.1.2",
"eslint": "^8.39.0",
"eslint": "^8.40.0",
"nuxt": "^3.4.3",
"nuxt-component-meta": "^0.5.1",
"nuxt-lodash": "^2.4.1",
"standard-version": "^9.5.0",
"unbuild": "^1.2.1",
"vue-tsc": "^1.6.3"
},
"volta": {
"node": "18.16.0"
"vue-tsc": "1.6.3"
}
}

View File

@@ -1,10 +1,11 @@
import { defineNuxtModule, installModule, addComponentsDir, addImportsDir, createResolver, addPlugin } from '@nuxt/kit'
import { defineNuxtModule, installModule, addComponentsDir, addImportsDir, createResolver, addPlugin, resolvePath } from '@nuxt/kit'
import colors from 'tailwindcss/colors.js'
import { iconsPlugin, getIconCollections } from '@egoist/tailwindcss-icons'
import { name, version } from '../package.json'
import { colorsAsRegex, excludeColors } from './runtime/utils/colors'
import preset from './runtime/app.config'
import type { DeepPartial } from './runtime/types'
import appConfig from './runtime/app.config'
type DeepPartial<T> = Partial<{ [P in keyof T]: DeepPartial<T[P]> | { [key: string]: string } }>
// @ts-ignore
delete colors.lightBlue
@@ -23,7 +24,7 @@ declare module 'nuxt/schema' {
primary?: string
gray?: string
colors?: string[]
} & DeepPartial<typeof preset.ui>
} & DeepPartial<typeof appConfig.ui>
}
}
@@ -64,8 +65,9 @@ export default defineNuxtModule<ModuleOptions>({
nuxt.options.css.push(resolve(runtimeDir, 'ui.css'))
const appConfigFile = await resolvePath(resolve(runtimeDir, 'app.config'))
nuxt.hook('app:resolve', (app) => {
app.configs.push(resolve(runtimeDir, 'app.config.ts'))
app.configs.push(appConfigFile)
})
// @ts-ignore
@@ -119,7 +121,7 @@ export default defineNuxtModule<ModuleOptions>({
}
tailwindConfig.safelist = tailwindConfig.safelist || []
tailwindConfig.safelist.push(...[{
tailwindConfig.safelist.push(...['bg-gray-400', {
pattern: new RegExp(`bg-(${safeColorsAsRegex})-(50|400|500)`)
}, {
pattern: new RegExp(`bg-(${safeColorsAsRegex})-500`),

View File

@@ -17,7 +17,7 @@ const avatar = {
'3xl': 'h-20 w-20 text-3xl'
},
chip: {
base: 'absolute block rounded-full ring-2 ring-white dark:ring-gray-900',
base: 'absolute block rounded-full ring-1 ring-white dark:ring-gray-900',
position: {
'top-right': 'top-0 right-0',
'bottom-right': 'bottom-0 right-0',
@@ -40,7 +40,7 @@ const avatar = {
}
},
default: {
size: 'md',
size: 'sm',
chipVariant: 'solid',
chipPosition: 'top-right'
}
@@ -57,22 +57,23 @@ const badge = {
rounded: 'rounded-md',
font: 'font-medium',
size: {
sm: 'text-xs px-1.5 py-0.5',
md: 'text-xs px-2 py-1',
lg: 'text-xs px-2.5 py-1.5'
xs: 'text-xs px-1.5 py-0.5',
sm: 'text-xs px-2 py-1',
md: 'text-sm px-2 py-1',
lg: 'text-sm px-2.5 py-1.5'
},
variant: {
solid: 'bg-{color}-50 dark:bg-{color}-400 dark:bg-opacity-10 text-{color}-500 dark:text-{color}-400 ring-1 ring-inset ring-{color}-500 dark:ring-{color}-400 ring-opacity-10 dark:ring-opacity-20'
},
default: {
size: 'md',
size: 'sm',
variant: 'solid',
color: 'primary'
}
}
const button = {
base: 'focus:outline-none focus-visible:outline-0 disabled:cursor-not-allowed disabled:opacity-75',
base: 'focus:outline-none focus-visible:outline-0 disabled:cursor-not-allowed disabled:opacity-75 flex-shrink-0',
font: 'font-medium',
rounded: 'rounded-md',
size: {
@@ -91,7 +92,7 @@ const button = {
lg: 'gap-x-2',
xl: 'gap-x-2'
},
spacing: {
padding: {
'2xs': 'px-2 py-1',
xs: 'px-2.5 py-1.5',
sm: 'px-3 py-1.5',
@@ -114,8 +115,6 @@ const button = {
},
gray: {
solid: 'shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-700 text-gray-700 dark:text-gray-200 bg-gray-50 hover:bg-gray-100 disabled:bg-gray-50 dark:bg-gray-800 dark:hover:bg-gray-700/50 dark:disabled:bg-gray-800 focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400',
// TODO: For Volta
// 'outline-ghost': 'text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800 hover:ring-1 ring-inset ring-gray-300 dark:ring-gray-700',
ghost: 'text-gray-700 dark:text-gray-200 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-gray-800 focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400',
link: 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 underline-offset-4 hover:underline focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400'
},
@@ -166,9 +165,12 @@ const dropdown = {
ring: 'ring-1 ring-gray-200 dark:ring-gray-700',
base: 'focus:outline-none',
divide: 'divide-y divide-gray-200 dark:divide-gray-700',
spacing: 'p-1',
padding: 'p-1',
item: {
base: 'group flex items-center gap-2 px-2 py-1.5 text-sm w-full rounded-md',
base: 'group flex items-center gap-2 w-full',
rounded: 'rounded-md',
padding: 'px-2 py-1.5',
size: 'text-sm',
active: 'bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-white',
inactive: 'text-gray-700 dark:text-gray-200',
disabled: 'cursor-not-allowed opacity-50',
@@ -181,7 +183,7 @@ const dropdown = {
base: 'flex-shrink-0',
size: '3xs'
},
shortcuts: 'hidden md:inline-flex flex-shrink-0 text-xs font-semibold text-gray-500 dark:text-gray-400 ml-auto'
shortcuts: 'hidden md:inline-flex flex-shrink-0 gap-0.5 ml-auto'
},
transition: {
enterActiveClass: 'transition duration-100 ease-out',
@@ -197,6 +199,23 @@ const dropdown = {
}
}
const kbd = {
base: 'inline-flex items-center justify-center text-gray-900 dark:text-white',
padding: 'px-1',
size: {
xs: 'h-4 min-w-[16px] text-[10px]',
sm: 'h-5 min-w-[20px] text-[11px]',
md: 'h-6 min-w-[24px] text-[12px]'
},
rounded: 'rounded',
font: 'font-medium font-sans',
background: 'bg-gray-100 dark:bg-gray-800',
ring: 'ring-1 ring-gray-300 dark:ring-gray-700 ring-inset',
default: {
size: 'sm'
}
}
// Forms
const input = {
@@ -219,7 +238,7 @@ const input = {
lg: 'gap-x-2',
xl: 'gap-x-2'
},
spacing: {
padding: {
'2xs': 'px-2 py-1',
xs: 'px-2.5 py-1.5',
sm: 'px-3 py-1.5',
@@ -228,7 +247,7 @@ const input = {
xl: 'px-4 py-3'
},
leading: {
spacing: {
padding: {
'2xs': 'pl-[26px]',
xs: 'pl-8',
sm: 'pl-9',
@@ -238,7 +257,7 @@ const input = {
}
},
trailing: {
spacing: {
padding: {
'2xs': 'pr-[26px]',
xs: 'pr-8',
sm: 'pr-9',
@@ -250,7 +269,7 @@ const input = {
appearance: {
white: 'border-0 bg-white dark:bg-gray-900 text-gray-900 dark:text-white rounded-md shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400 placeholder:text-gray-400 dark:placeholder:text-gray-500',
gray: 'border-0 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white rounded-md shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400 placeholder:text-gray-400 dark:placeholder:text-gray-500',
none: 'border-0 bg-transparent focus:ring-0 focus:shadow-none'
none: 'border-0 bg-transparent focus:ring-0 focus:shadow-none placeholder:text-gray-400 dark:placeholder:text-gray-500'
},
icon: {
base: 'text-gray-400 dark:text-gray-500',
@@ -264,7 +283,7 @@ const input = {
},
leading: {
wrapper: 'absolute inset-y-0 left-0 flex items-center pointer-events-none',
spacing: {
padding: {
'2xs': 'pl-2',
xs: 'pl-2.5',
sm: 'pl-3',
@@ -275,7 +294,7 @@ const input = {
},
trailing: {
wrapper: 'absolute inset-y-0 right-0 flex items-center pointer-events-none',
spacing: {
padding: {
'2xs': 'pr-2',
xs: 'pr-2.5',
sm: 'pr-3',
@@ -304,11 +323,20 @@ const inputGroup = {
}
const textarea = {
...input
...input,
default: {
size: 'sm',
appearance: 'white'
}
}
const select = {
...input
...input,
default: {
size: 'sm',
appearance: 'white',
trailingIcon: 'i-heroicons-chevron-down-20-solid'
}
}
const selectMenu = {
@@ -320,14 +348,19 @@ const selectMenu = {
background: 'bg-white dark:bg-gray-800',
shadow: 'shadow-lg',
rounded: 'rounded-md',
spacing: 'p-1',
padding: 'p-1',
ring: 'ring-1 ring-gray-200 dark:ring-gray-700',
input: 'block w-[calc(100%+0.5rem)] focus:ring-transparent text-sm px-3 py-1.5 u-text-gray-700 bg-white dark:bg-gray-800 border-0 border-b border-gray-200 dark:border-gray-700 focus:border-inherit sticky -top-1 -mt-1 mb-1 -mx-1 z-10 placeholder-gray-400 dark:placeholder-gray-500',
input: 'block w-[calc(100%+0.5rem)] focus:ring-transparent text-sm px-3 py-1.5 text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-800 border-0 border-b border-gray-200 dark:border-gray-700 focus:border-inherit sticky -top-1 -mt-1 mb-1 -mx-1 z-10 placeholder-gray-400 dark:placeholder-gray-500',
option: {
base: 'cursor-default select-none relative px-2 py-1.5 rounded-md text-sm text-gray-900 dark:text-white flex items-center justify-between gap-1',
container: 'flex items-center gap-2',
base: 'cursor-default select-none relative flex items-center justify-between gap-1',
rounded: 'rounded-md',
padding: 'px-2 py-1.5',
size: 'text-sm',
color: 'text-gray-900 dark:text-white',
container: 'flex items-center gap-2 min-w-0',
active: 'bg-gray-100 dark:bg-gray-900',
inactive: '',
selected: 'pr-7',
disabled: 'cursor-not-allowed opacity-50',
empty: 'text-sm text-gray-400 dark:text-gray-500 px-2 py-1.5',
icon: {
@@ -335,16 +368,17 @@ const selectMenu = {
active: 'text-gray-900 dark:text-white',
inactive: 'text-gray-400 dark:text-gray-500'
},
selectedIcon: {
wrapper: 'absolute inset-y-0 right-0 flex items-center',
padding: 'pr-2',
base: 'h-4 w-4 text-gray-900 dark:text-white flex-shrink-0'
},
avatar: {
base: 'flex-shrink-0',
size: '3xs'
},
chip: {
base: 'flex-shrink-0 w-2 h-2 mx-1 rounded-full'
},
selected: {
wrapper: 'absolute inset-y-0 right-0 flex items-center pr-2',
icon: 'h-4 w-4 text-gray-900 dark:text-white flex-shrink-0'
}
},
transition: {
@@ -356,13 +390,14 @@ const selectMenu = {
placement: 'bottom-end'
},
default: {
selectedIcon: 'i-heroicons-check-20-solid'
selectedIcon: 'i-heroicons-check-20-solid',
trailingIcon: 'i-heroicons-chevron-down-20-solid'
}
}
const radio = {
wrapper: 'relative flex items-start',
base: 'h-4 w-4 text-primary-500 dark:text-primary-400 focus:ring-2 focus:ring-offset-2 bg-white dark:bg-gray-900 dark:checked:bg-current dark:checked:border-transparent focus:ring-primary-500 dark:focus:ring-primary-400 focus:ring-offset-white dark:focus:ring-offset-gray-900 border-gray-300 dark:border-gray-700 disabled:opacity-50 disabled:cursor-not-allowed',
base: 'h-4 w-4 text-primary-500 dark:text-primary-400 focus-visible:ring-2 focus-visible:ring-offset-2 bg-white dark:bg-gray-900 dark:checked:bg-current dark:checked:border-transparent focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 focus:ring-offset-white dark:focus:ring-offset-gray-900 border-gray-300 dark:border-gray-700 disabled:opacity-50 disabled:cursor-not-allowed focus:ring-0 focus:ring-transparent',
label: 'font-medium text-gray-700 dark:text-gray-200',
required: 'text-red-400',
help: 'text-gray-500 dark:text-gray-400'
@@ -374,12 +409,12 @@ const checkbox = {
}
const toggle = {
base: 'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 dark:focus:ring-primary-400 focus:ring-offset-white dark:focus:ring-offset-gray-900',
base: 'relative inline-flex flex-shrink-0 h-5 w-9 border-2 border-transparent rounded-full cursor-pointer disabled:cursor-not-allowed focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900',
active: 'bg-primary-500 dark:bg-primary-400',
inactive: 'bg-gray-200 dark:bg-gray-700',
container: {
base: 'pointer-events-none relative inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200',
active: 'translate-x-5',
base: 'pointer-events-none relative inline-block h-4 w-4 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200',
active: 'translate-x-4',
inactive: 'translate-x-0'
},
icon: {
@@ -396,45 +431,46 @@ const toggle = {
const card = {
base: 'overflow-hidden',
background: 'bg-white dark:bg-gray-900',
divide: 'divide-y divide-gray-200 dark:divide-gray-700',
ring: 'ring-1 ring-gray-200 dark:ring-gray-700',
divide: 'divide-y divide-gray-200 dark:divide-gray-800',
ring: 'ring-1 ring-gray-200 dark:ring-gray-800',
rounded: 'rounded-lg',
shadow: 'shadow',
body: {
base: '',
background: '',
spacing: 'px-4 py-5 sm:p-6'
padding: 'px-4 py-5 sm:p-6'
},
header: {
base: '',
background: '',
spacing: 'px-4 py-5 sm:px-6'
padding: 'px-4 py-5 sm:px-6'
},
footer: {
base: '',
background: '',
spacing: 'px-4 py-4 sm:px-6'
padding: 'px-4 py-4 sm:px-6'
}
}
const container = {
base: 'mx-auto',
spacing: 'px-4 sm:px-6 lg:px-8',
padding: 'px-4 sm:px-6 lg:px-8',
constrained: 'max-w-7xl'
}
// Navigation
const verticalNavigation = {
wrapper: 'relative z-0',
base: 'group flex items-center gap-2 text-sm font-medium rounded-md w-full relative focus:outline-none after:absolute after:inset-px after:z-[-1] after:rounded-md disabled:cursor-not-allowed disabled:opacity-75',
spacing: 'px-3 py-1.5',
active: 'u-text-gray-900 after:bg-gray-100 dark:after:bg-gray-800',
inactive: 'u-text-gray-500 hover:u-text-gray-900 hover:after:bg-gray-50 dark:hover:after:bg-gray-800/50 focus-visible:after:bg-gray-50 dark:focus-visible:after:bg-gray-800/50',
wrapper: 'relative',
base: 'group flex items-center gap-2 text-sm font-medium rounded-md w-full relative focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 focus-visible:before:ring-inset focus-visible:before:ring-1 focus-visible:before:ring-primary-500 dark:focus-visible:before:ring-primary-400 before:absolute before:inset-px before:rounded-md disabled:cursor-not-allowed disabled:opacity-75',
padding: 'px-3 py-1.5',
active: 'text-gray-900 dark:text-white before:bg-gray-100 dark:before:bg-gray-800 ',
inactive: 'text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:before:bg-gray-50 dark:hover:before:bg-gray-800/50',
label: 'truncate relative',
icon: {
base: 'flex-shrink-0 w-4 h-4',
active: 'u-text-gray-700',
inactive: 'u-text-gray-400 group-hover:u-text-gray-700'
active: 'text-gray-700 dark:text-gray-200',
inactive: 'text-gray-400 dark:text-gray-500 group-hover:text-gray-700 dark:group-hover:text-gray-200'
},
avatar: {
base: 'flex-shrink-0',
@@ -443,7 +479,7 @@ const verticalNavigation = {
badge: {
base: 'ml-auto inline-block py-0.5 px-2 text-xs rounded-md -mr-1 -my-0.5',
active: 'bg-white dark:bg-gray-900',
inactive: 'u-bg-gray-100 u-text-gray-600 group-hover:bg-white dark:group-hover:bg-gray-900'
inactive: 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-300 group-hover:bg-white dark:group-hover:bg-gray-900'
}
}
@@ -452,9 +488,15 @@ const commandPalette = {
container: 'relative flex-1 overflow-y-auto divide-y divide-gray-100 dark:divide-gray-800 scroll-py-2',
input: {
wrapper: 'relative flex items-center',
base: 'w-full h-12 px-4 placeholder-gray-400 dark:placeholder-gray-500 bg-transparent border-0 text-gray-900 dark:text-white focus:ring-0 sm:text-sm',
spacing: 'pl-10',
icon: 'pointer-events-none absolute left-4 h-4 w-4 text-gray-400 dark:text-gray-500',
base: 'w-full placeholder-gray-400 dark:placeholder-gray-500 bg-transparent border-0 text-gray-900 dark:text-white focus:ring-0',
padding: 'px-4',
height: 'h-12',
size: 'sm:text-sm',
icon: {
base: 'pointer-events-none absolute left-4 text-gray-400 dark:text-gray-500',
size: 'h-4 w-4',
padding: 'pl-10'
},
close: 'absolute right-4'
},
empty: {
@@ -480,6 +522,9 @@ const commandPalette = {
active: 'text-gray-900 dark:text-white',
inactive: 'text-gray-400 dark:text-gray-500'
},
selectedIcon: {
base: 'h-4 w-4 text-gray-900 dark:text-white flex-shrink-0'
},
avatar: {
base: 'flex-shrink-0',
size: '3xs'
@@ -488,10 +533,7 @@ const commandPalette = {
base: 'flex-shrink-0 w-2 h-2 mx-1 rounded-full'
},
disabled: 'opacity-50',
selected: {
icon: 'h-4 w-4 text-gray-900 dark:text-white flex-shrink-0'
},
shortcuts: 'hidden md:inline-flex flex-shrink-0 text-xs font-semibold text-gray-500 dark:text-gray-400'
shortcuts: 'hidden md:inline-flex flex-shrink-0 gap-0.5'
},
active: 'flex-shrink-0 text-gray-500 dark:text-gray-400',
inactive: 'flex-shrink-0 text-gray-500 dark:text-gray-400'
@@ -514,11 +556,11 @@ const modal = {
wrapper: 'relative z-50',
inner: 'fixed inset-0 overflow-y-auto',
container: 'flex min-h-full items-end sm:items-center justify-center text-center',
spacing: 'p-4 sm:p-0',
padding: 'p-4 sm:p-0',
base: 'relative text-left overflow-hidden sm:my-8 w-full flex flex-col',
overlay: {
base: 'fixed inset-0 transition-opacity',
background: 'bg-gray-500/75 dark:bg-gray-600/75',
background: 'bg-gray-200/75 dark:bg-gray-800/75',
transition: {
enter: 'ease-out duration-300',
enterFrom: 'opacity-0',
@@ -548,7 +590,7 @@ const slideover = {
wrapper: 'fixed inset-0 flex z-50',
overlay: {
base: 'fixed inset-0 transition-opacity',
background: 'bg-gray-500/75 dark:bg-gray-600/75',
background: 'bg-gray-200/75 dark:bg-gray-800/75',
transition: {
enter: 'ease-in-out duration-500',
enterFrom: 'opacity-0',
@@ -562,11 +604,12 @@ const slideover = {
background: 'bg-white dark:bg-gray-900',
ring: '',
rounded: '',
padding: '',
shadow: 'shadow-xl',
width: 'w-screen max-w-md',
transition: {
enter: 'transform transition ease-in-out duration-500 sm:duration-700',
leave: 'transform transition ease-in-out duration-500 sm:duration-700'
enter: 'transform transition ease-in-out duration-300',
leave: 'transform transition ease-in-out duration-200'
}
}
@@ -579,7 +622,7 @@ const tooltip = {
rounded: 'rounded',
ring: 'ring-1 ring-gray-200 dark:ring-gray-800',
base: 'invisible lg:visible h-6 px-2 py-1 text-xs font-normal truncate',
shortcuts: 'hidden md:inline-flex items-center justify-end flex-shrink-0 gap-0.5 ml-1',
shortcuts: 'hidden md:inline-flex flex-shrink-0 gap-0.5',
transition: {
enterActiveClass: 'transition ease-out duration-200',
enterFromClass: 'opacity-0 translate-y-1',
@@ -685,6 +728,7 @@ export default {
button,
buttonGroup,
dropdown,
kbd,
input,
inputGroup,
textarea,

View File

@@ -45,7 +45,7 @@ export default defineComponent({
type: String,
default: null,
validator (value: string) {
return appConfig.ui.colors.includes(value)
return ['gray', ...appConfig.ui.colors].includes(value)
}
},
chipVariant: {

View File

@@ -6,13 +6,13 @@
:aria-label="ariaLabel"
v-bind="buttonProps"
>
<Icon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="leadingIconClass" aria-hidden="true" />
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="leadingIconClass" aria-hidden="true" />
<slot>
<span v-if="label" :class="[truncate ? 'text-left break-all line-clamp-1' : '']">
{{ label }}
</span>
</slot>
<Icon v-if="isTrailing && trailingIconName" :name="trailingIconName" :class="trailingIconClass" aria-hidden="true" />
<UIcon v-if="isTrailing && trailingIconName" :name="trailingIconName" :class="trailingIconClass" aria-hidden="true" />
</component>
</template>
@@ -21,7 +21,7 @@ import { ref, computed, defineComponent, useSlots } from 'vue'
import type { PropType } from 'vue'
import type { RouteLocationNormalized, RouteLocationRaw } from 'vue-router'
import { defu } from 'defu'
import Icon from '../elements/Icon.vue'
import UIcon from '../elements/Icon.vue'
import { classNames } from '../../utils'
import { NuxtLink } from '#components'
import { useAppConfig } from '#imports'
@@ -33,7 +33,7 @@ import appConfig from '#build/app.config'
export default defineComponent({
components: {
Icon
UIcon
},
props: {
type: {
@@ -78,7 +78,10 @@ export default defineComponent({
type: String,
default: () => appConfig.ui.button.default.variant,
validator (value: string) {
return Object.keys(appConfig.ui.button.variant).includes(value)
return [
...Object.keys(appConfig.ui.button.variant),
...Object.values(appConfig.ui.button.color).flatMap(value => Object.keys(value))
].includes(value)
}
},
icon: {
@@ -175,7 +178,7 @@ export default defineComponent({
ui.value.rounded,
ui.value.size[props.size],
ui.value.gap[props.size],
props.padded && ui.value[isSquare.value ? 'square' : 'spacing'][props.size],
props.padded && ui.value[isSquare.value ? 'square' : 'padding'][props.size],
variant?.replaceAll('{color}', props.color),
props.block ? 'w-full flex justify-center items-center' : 'inline-flex items-center'
)

View File

@@ -18,22 +18,22 @@
<div v-if="open && items.length" ref="container" :class="[ui.container, ui.width]" @mouseover="onMouseOver">
<transition appear v-bind="ui.transition">
<MenuItems :class="[ui.base, ui.divide, ui.ring, ui.rounded, ui.shadow, ui.background]" static>
<div v-for="(subItems, index) of items" :key="index" :class="ui.spacing">
<div v-for="(subItems, index) of items" :key="index" :class="ui.padding">
<MenuItem v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ active, disabled: itemDisabled }" :disabled="item.disabled">
<Component
v-bind="omit(item, ['click'])"
:is="(item.to && NuxtLink) || (item.click && 'button') || 'div'"
:class="resolveItemClass({ active, disabled: itemDisabled })"
:class="[ui.item.base, ui.item.padding, ui.item.size, ui.item.rounded, active ? ui.item.active : ui.item.inactive, itemDisabled && ui.item.disabled]"
@click="item.click"
>
<slot :name="item.slot || 'item'" :item="item">
<Icon v-if="item.icon" :name="item.icon" :class="[ui.item.icon.base, active ? ui.item.icon.active : ui.item.icon.inactive, item.iconClass]" />
<Avatar v-else-if="item.avatar" v-bind="{ size: ui.item.avatar.size, ...item.avatar }" :class="ui.item.avatar.base" />
<UIcon v-if="item.icon" :name="item.icon" :class="[ui.item.icon.base, active ? ui.item.icon.active : ui.item.icon.inactive, item.iconClass]" />
<UAvatar v-else-if="item.avatar" v-bind="{ size: ui.item.avatar.size, ...item.avatar }" :class="ui.item.avatar.base" />
<span class="truncate">{{ item.label }}</span>
<span v-if="item.shortcuts?.length" :class="ui.item.shortcuts">
<kbd v-for="shortcut of item.shortcuts" :key="shortcut" class="font-sans">{{ shortcut }}</kbd>
<UKbd v-for="shortcut of item.shortcuts" :key="shortcut">{{ shortcut }}</UKbd>
</span>
</slot>
</Component>
@@ -51,9 +51,10 @@ import type { PropType } from 'vue'
import type { RouteLocationNormalized } from 'vue-router'
import { defineComponent, ref, computed, onMounted } from 'vue'
import { defu } from 'defu'
import Icon from '../elements/Icon.vue'
import Avatar from '../elements/Avatar.vue'
import { classNames, omit } from '../../utils'
import UIcon from '../elements/Icon.vue'
import UAvatar from '../elements/Avatar.vue'
import UKbd from '../elements/Kbd.vue'
import { omit } from '../../utils'
import { usePopper } from '../../composables/usePopper'
import type { Avatar as AvatarType } from '../../types/avatar'
import type { PopperOptions } from '../../types'
@@ -72,8 +73,9 @@ export default defineComponent({
MenuButton,
MenuItems,
MenuItem,
Icon,
Avatar
UIcon,
UAvatar,
UKbd
},
props: {
items: {
@@ -129,14 +131,6 @@ export default defineComponent({
const [trigger, container] = usePopper(popper.value)
function resolveItemClass ({ active, disabled }: { active: boolean, disabled: boolean }) {
return classNames(
ui.value.item.base,
active ? ui.value.item.active : ui.value.item.inactive,
disabled && ui.value.item.disabled
)
}
// https://github.com/tailwindlabs/headlessui/blob/f66f4926c489fc15289d528294c23a3dc2aee7b1/packages/%40headlessui-vue/src/components/menu/menu.ts#L131
const menuApi = ref<any>(null)
@@ -200,7 +194,6 @@ export default defineComponent({
ui,
trigger,
container,
resolveItemClass,
onMouseOver,
onMouseLeave,
omit,

View File

@@ -2,11 +2,15 @@
<span :class="name" />
</template>
<script setup lang="ts">
defineProps({
name: {
type: String,
required: true
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
props: {
name: {
type: String,
required: true
}
}
})
</script>

View File

@@ -0,0 +1,44 @@
<template>
<kbd :class="[ui.base, ui.size[size], ui.padding, ui.rounded, ui.font, ui.background, ui.ring]">
<slot />
</kbd>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue'
import type { PropType } from 'vue'
import { defu } from 'defu'
import { useAppConfig } from '#imports'
// TODO: Remove
// @ts-expect-error
import appConfig from '#build/app.config'
// const appConfig = useAppConfig()
export default defineComponent({
props: {
size: {
type: String,
default: () => appConfig.ui.kbd.default.size,
validator (value: string) {
return Object.keys(appConfig.ui.kbd.size).includes(value)
}
},
ui: {
type: Object as PropType<Partial<typeof appConfig.ui.kbd>>,
default: () => appConfig.ui.kbd
}
},
setup (props) {
// TODO: Remove
const appConfig = useAppConfig()
const ui = computed<Partial<typeof appConfig.ui.kbd>>(() => defu({}, props.ui, appConfig.ui.kbd))
return {
// eslint-disable-next-line vue/no-dupe-keys
ui
}
}
})
</script>

View File

@@ -13,16 +13,16 @@
:autocomplete="autocomplete"
:spellcheck="spellcheck"
:class="inputClass"
@input="onInput(($event.target as any).value)"
@input="onInput"
@focus="$emit('focus', $event)"
@blur="$emit('blur', $event)"
>
<slot />
<div v-if="isLeading && leadingIconName" :class="leadingIconClass">
<Icon :name="leadingIconName" :class="iconClass" />
<UIcon :name="leadingIconName" :class="iconClass" />
</div>
<div v-if="isTrailing && trailingIconName" :class="trailingIconClass">
<Icon :name="trailingIconName" :class="iconClass" />
<UIcon :name="trailingIconName" :class="iconClass" />
</div>
</div>
</template>
@@ -31,7 +31,7 @@
import { ref, computed, onMounted, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { defu } from 'defu'
import Icon from '../elements/Icon.vue'
import UIcon from '../elements/Icon.vue'
import { classNames } from '../../utils'
import { useAppConfig } from '#imports'
// TODO: Remove
@@ -42,7 +42,7 @@ import appConfig from '#build/app.config'
export default defineComponent({
components: {
Icon
UIcon
},
props: {
modelValue: {
@@ -147,8 +147,8 @@ export default defineComponent({
}
}
const onInput = (value: string) => {
emit('update:modelValue', value)
const onInput = (event: InputEvent) => {
emit('update:modelValue', (event.target as any).value)
}
onMounted(() => {
@@ -161,10 +161,10 @@ export default defineComponent({
return classNames(
ui.value.base,
ui.value.size[props.size],
ui.value.spacing[props.size],
ui.value.padding[props.size],
ui.value.appearance[props.appearance],
isLeading.value && ui.value.leading.spacing[props.size],
isTrailing.value && ui.value.trailing.spacing[props.size],
isLeading.value && ui.value.leading.padding[props.size],
isTrailing.value && ui.value.trailing.padding[props.size],
ui.value.custom
)
})
@@ -204,14 +204,14 @@ export default defineComponent({
const leadingIconClass = computed(() => {
return classNames(
ui.value.icon.leading.wrapper,
ui.value.icon.leading.spacing[props.size]
ui.value.icon.leading.padding[props.size]
)
})
const trailingIconClass = computed(() => {
return classNames(
ui.value.icon.trailing.wrapper,
ui.value.icon.trailing.spacing[props.size]
ui.value.icon.trailing.padding[props.size]
)
})

View File

@@ -7,7 +7,7 @@
:required="required"
:disabled="disabled"
:class="selectClass"
@input="onInput(($event.target as any).value)"
@input="onInput"
>
<template v-for="(option, index) in normalizedOptionsWithPlaceholder">
<optgroup
@@ -17,7 +17,7 @@
:label="option[textAttribute]"
>
<option
v-for="(childOption, index2) in (option.children as any[])"
v-for="(childOption, index2) in option.children"
:key="`${childOption[valueAttribute]}-${index}-${index2}`"
:value="childOption[valueAttribute]"
:selected="childOption[valueAttribute] === normalizedValue"
@@ -37,21 +37,21 @@
</select>
<div v-if="icon" :class="leadingIconClass">
<Icon :name="icon" :class="iconClass" />
<UIcon :name="icon" :class="iconClass" />
</div>
<span :class="trailingIconClass">
<Icon name="i-heroicons-chevron-down-20-solid" :class="iconClass" aria-hidden="true" />
<span v-if="trailingIcon" :class="trailingIconClass">
<UIcon :name="trailingIcon" :class="iconClass" aria-hidden="true" />
</span>
</div>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue'
import type { PropType } from 'vue'
import type { PropType, ComputedRef } from 'vue'
import { get } from 'lodash-es'
import { defu } from 'defu'
import Icon from '../elements/Icon.vue'
import UIcon from '../elements/Icon.vue'
import { classNames } from '../../utils'
import { useAppConfig } from '#imports'
// TODO: Remove
@@ -62,7 +62,7 @@ import appConfig from '#build/app.config'
export default defineComponent({
components: {
Icon
UIcon
},
props: {
modelValue: {
@@ -89,6 +89,10 @@ export default defineComponent({
type: String,
default: null
},
trailingIcon: {
type: String,
default: () => appConfig.ui.select.default.trailingIcon
},
options: {
type: Array,
default: () => []
@@ -127,8 +131,8 @@ export default defineComponent({
const ui = computed<Partial<typeof appConfig.ui.select>>(() => defu({}, props.ui, appConfig.ui.select))
const onInput = (value: string) => {
emit('update:modelValue', value)
const onInput = (event: InputEvent) => {
emit('update:modelValue', (event.target as any).value)
}
const guessOptionValue = (option: any) => {
@@ -158,7 +162,7 @@ export default defineComponent({
return props.options.map(option => normalizeOption(option))
})
const normalizedOptionsWithPlaceholder = computed(() => {
const normalizedOptionsWithPlaceholder: ComputedRef<{ disabled?: boolean, children: { disabled?: boolean }[] }[]> = computed(() => {
if (!props.placeholder) {
return normalizedOptions.value
}
@@ -187,10 +191,10 @@ export default defineComponent({
return classNames(
ui.value.base,
ui.value.size[props.size],
ui.value.spacing[props.size],
ui.value.padding[props.size],
ui.value.appearance[props.appearance],
!!props.icon && ui.value.leading.spacing[props.size],
ui.value.trailing.spacing[props.size],
!!props.icon && ui.value.leading.padding[props.size],
ui.value.trailing.padding[props.size],
ui.value.custom
)
})
@@ -205,14 +209,14 @@ export default defineComponent({
const leadingIconClass = computed(() => {
return classNames(
ui.value.icon.leading.wrapper,
ui.value.icon.leading.spacing[props.size]
ui.value.icon.leading.padding[props.size]
)
})
const trailingIconClass = computed(() => {
return classNames(
ui.value.icon.trailing.wrapper,
ui.value.icon.trailing.spacing[props.size]
ui.value.icon.trailing.padding[props.size]
)
})

View File

@@ -11,8 +11,7 @@
:class="ui.wrapper"
@update:model-value="onUpdate"
>
<!-- TODO: check that `name` fixes required -->
<!-- <input :value="modelValue" :required="required" class="absolute inset-0 w-px opacity-0 cursor-default" tabindex="-1" aria-hidden="true"> -->
<input :value="modelValue" :required="required" class="absolute inset-0 w-px opacity-0 cursor-default" tabindex="-1" aria-hidden="true">
<component
:is="searchable ? 'ComboboxButton' : 'ListboxButton'"
@@ -24,16 +23,16 @@
<slot :open="open" :disabled="disabled">
<button :class="selectMenuClass" :disabled="disabled" type="button">
<span v-if="icon" :class="leadingIconClass">
<Icon :name="icon" :class="iconClass" />
<UIcon :name="icon" :class="iconClass" />
</span>
<slot name="label">
<span v-if="modelValue" class="block truncate">{{ typeof modelValue === 'string' ? modelValue : (modelValue as any)[optionAttribute] }}</span>
<span v-if="modelValue" class="block truncate">{{ typeof modelValue === 'string' ? modelValue : modelValue[optionAttribute] }}</span>
<span v-else class="block truncate text-gray-400 dark:text-gray-500">{{ placeholder || '&nbsp;' }}</span>
</slot>
<span :class="trailingIconClass">
<Icon name="i-heroicons-chevron-down-20-solid" :class="iconClass" aria-hidden="true" />
<span v-if="trailingIcon" :class="trailingIconClass">
<UIcon :name="trailingIcon" :class="iconClass" aria-hidden="true" />
</span>
</button>
</slot>
@@ -41,7 +40,7 @@
<div v-if="open" ref="container" :class="[ui.container, ui.width]">
<transition v-bind="ui.transition">
<component :is="searchable ? 'ComboboxOptions' : 'ListboxOptions'" static :class="[ui.base, ui.divide, ui.ring, ui.rounded, ui.shadow, ui.background, ui.spacing, ui.height]">
<component :is="searchable ? 'ComboboxOptions' : 'ListboxOptions'" static :class="[ui.base, ui.divide, ui.ring, ui.rounded, ui.shadow, ui.background, ui.padding, ui.height]">
<ComboboxInput
v-if="searchable"
ref="searchInput"
@@ -62,11 +61,11 @@
:value="option"
:disabled="option.disabled"
>
<li :class="resolveOptionClass({ active, disabled: optionDisabled })">
<li :class="[ui.option.base, ui.option.rounded, ui.option.padding, ui.option.size, ui.option.color, active ? ui.option.active : ui.option.inactive, selected && ui.option.selected, optionDisabled && ui.option.disabled]">
<div :class="ui.option.container">
<slot name="option" :option="option" :active="active" :selected="selected">
<Icon v-if="option.icon" :name="option.icon" :class="[ui.option.icon.base, active ? ui.option.icon.active : ui.option.icon.inactive, option.iconClass]" aria-hidden="true" />
<Avatar
<UIcon v-if="option.icon" :name="option.icon" :class="[ui.option.icon.base, active ? ui.option.icon.active : ui.option.icon.inactive, option.iconClass]" aria-hidden="true" />
<UAvatar
v-else-if="option.avatar"
v-bind="{ size: ui.option.avatar.size, ...option.avatar }"
:class="ui.option.avatar.base"
@@ -78,14 +77,14 @@
</slot>
</div>
<span v-if="selected" :class="ui.option.selected.wrapper">
<Icon :name="selectedIcon" :class="ui.option.selected.icon" aria-hidden="true" />
<span v-if="selected" :class="[ui.option.selectedIcon.wrapper, ui.option.selectedIcon.padding]">
<UIcon :name="selectedIcon" :class="ui.option.selectedIcon.base" aria-hidden="true" />
</span>
</li>
</component>
<component :is="searchable ? 'ComboboxOption' : 'ListboxOption'" v-if="creatable && queryOption && !filteredOptions.length" v-slot="{ active, selected }" :value="queryOption" as="template">
<li :class="resolveOptionClass({ active })">
<li :class="[ui.option.base, ui.option.rounded, ui.option.padding, ui.option.size, ui.option.color, active ? ui.option.active : ui.option.inactive]">
<div :class="ui.option.container">
<slot name="option-create" :option="queryOption" :active="active" :selected="selected">
<span class="block truncate">Create "{{ queryOption[optionAttribute] }}"</span>
@@ -109,8 +108,8 @@ import { ref, computed, watch, defineComponent } from 'vue'
import type { PropType, ComponentPublicInstance } from 'vue'
import { defu } from 'defu'
import { Combobox, ComboboxButton, ComboboxOptions, ComboboxOption, ComboboxInput, Listbox, ListboxButton, ListboxOptions, ListboxOption } from '@headlessui/vue'
import Icon from '../elements/Icon.vue'
import Avatar from '../elements/Avatar.vue'
import UIcon from '../elements/Icon.vue'
import UAvatar from '../elements/Avatar.vue'
import { classNames } from '../../utils'
import { usePopper } from '../../composables/usePopper'
import type { PopperOptions } from '../../types'
@@ -132,8 +131,8 @@ export default defineComponent({
ListboxButton,
ListboxOptions,
ListboxOption,
Icon,
Avatar
UIcon,
UAvatar
},
props: {
modelValue: {
@@ -160,6 +159,10 @@ export default defineComponent({
type: String,
default: null
},
trailingIcon: {
type: String,
default: () => appConfig.ui.selectMenu.default.trailingIcon
},
selectedIcon: {
type: String,
default: () => appConfig.ui.selectMenu.default.selectedIcon
@@ -240,10 +243,10 @@ export default defineComponent({
'text-left cursor-default',
uiSelect.value.size[props.size],
uiSelect.value.gap[props.size],
uiSelect.value.spacing[props.size],
uiSelect.value.padding[props.size],
uiSelect.value.appearance[props.appearance],
!!props.icon && uiSelect.value.leading.spacing[props.size],
uiSelect.value.trailing.spacing[props.size],
!!props.icon && uiSelect.value.leading.padding[props.size],
uiSelect.value.trailing.padding[props.size],
uiSelect.value.custom,
'inline-flex items-center'
)
@@ -259,14 +262,14 @@ export default defineComponent({
const leadingIconClass = computed(() => {
return classNames(
uiSelect.value.icon.leading.wrapper,
uiSelect.value.icon.leading.spacing[props.size]
uiSelect.value.icon.leading.padding[props.size]
)
})
const trailingIconClass = computed(() => {
return classNames(
uiSelect.value.icon.trailing.wrapper,
uiSelect.value.icon.trailing.spacing[props.size]
uiSelect.value.icon.trailing.padding[props.size]
)
})
@@ -292,14 +295,6 @@ export default defineComponent({
}
})
function resolveOptionClass ({ active, disabled }: { active: boolean, disabled?: boolean }) {
return classNames(
ui.value.option.base,
active ? ui.value.option.active : ui.value.option.inactive,
disabled && ui.value.option.disabled
)
}
function onUpdate (event: any) {
if (query.value && searchInput.value?.$el) {
query.value = ''
@@ -321,7 +316,6 @@ export default defineComponent({
filteredOptions,
queryOption,
query,
resolveOptionClass,
onUpdate
}
}

View File

@@ -11,7 +11,7 @@
:placeholder="placeholder"
:autocomplete="autocomplete"
:class="textareaClass"
@input="onInput(($event.target as any).value)"
@input="onInput"
@focus="$emit('focus', $event)"
@blur="$emit('blur', $event)"
/>
@@ -128,10 +128,10 @@ export default defineComponent({
}
}
const onInput = (value: string) => {
const onInput = (event: InputEvent) => {
autoResize()
emit('update:modelValue', value)
emit('update:modelValue', (event.target as any).value)
}
watch(() => props.modelValue, () => {
@@ -149,7 +149,7 @@ export default defineComponent({
return classNames(
ui.value.base,
ui.value.size[props.size],
ui.value.spacing[props.size],
ui.value.padding[props.size],
ui.value.appearance[props.appearance],
!props.resize && 'resize-none',
ui.value.custom

View File

@@ -5,10 +5,10 @@
>
<span :class="[active ? ui.container.active : ui.container.inactive, ui.container.base]">
<span v-if="iconOn" :class="[active ? ui.icon.active : ui.icon.inactive, ui.icon.base]" aria-hidden="true">
<Icon :name="iconOn" :class="ui.icon.on" />
<UIcon :name="iconOn" :class="ui.icon.on" />
</span>
<span v-if="iconOff" :class="[active ? ui.icon.active : ui.icon.inactive, ui.icon.base]" aria-hidden="true">
<Icon :name="iconOff" :class="ui.icon.off" />
<span v-if="iconOff" :class="[active ? ui.icon.inactive : ui.icon.active, ui.icon.base]" aria-hidden="true">
<UIcon :name="iconOff" :class="ui.icon.off" />
</span>
</span>
</Switch>
@@ -19,7 +19,7 @@ import { computed, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { defu } from 'defu'
import { Switch } from '@headlessui/vue'
import Icon from '../elements/Icon.vue'
import UIcon from '../elements/Icon.vue'
import { useAppConfig } from '#imports'
// TODO: Remove
// @ts-expect-error
@@ -31,7 +31,7 @@ export default defineComponent({
components: {
// eslint-disable-next-line vue/no-reserved-component-names
Switch,
Icon
UIcon
},
props: {
modelValue: {

View File

@@ -4,13 +4,13 @@
:class="[ui.base, ui.rounded, ui.divide, ui.ring, ui.shadow, ui.background]"
v-bind="$attrs"
>
<div v-if="$slots.header" :class="[ui.header.base, ui.header.spacing, ui.header.background]">
<div v-if="$slots.header" :class="[ui.header.base, ui.header.padding, ui.header.background]">
<slot name="header" />
</div>
<div :class="[ui.body.base, ui.body.spacing, ui.body.background]">
<div :class="[ui.body.base, ui.body.padding, ui.body.background]">
<slot />
</div>
<div v-if="$slots.footer" :class="[ui.footer.base, ui.footer.spacing, ui.footer.background]">
<div v-if="$slots.footer" :class="[ui.footer.base, ui.footer.padding, ui.footer.background]">
<slot name="footer" />
</div>
</component>

View File

@@ -1,5 +1,5 @@
<template>
<div :class="[ui.base, ui.spacing, ui.constrained]">
<div :class="[ui.base, ui.padding, ui.constrained]">
<slot />
</div>
</template>

View File

@@ -8,18 +8,18 @@
@update:model-value="onSelect"
>
<div :class="ui.wrapper">
<div v-if="searchable" :class="ui.input.wrapper">
<Icon v-if="icon" :name="icon" :class="ui.input.icon" aria-hidden="true" />
<div v-show="searchable" :class="ui.input.wrapper">
<UIcon v-if="icon" :name="icon" :class="[ui.input.icon.base, ui.input.icon.size]" aria-hidden="true" />
<ComboboxInput
ref="comboboxInput"
:value="query"
:class="[ui.input.base, icon && ui.input.spacing]"
:class="[ui.input.base, ui.input.size, ui.input.height, ui.input.padding, icon && ui.input.icon.padding]"
:placeholder="placeholder"
autocomplete="off"
@change="query = $event.target.value"
/>
<Button
<UButton
v-if="close"
v-bind="close"
:class="ui.input.close"
@@ -53,7 +53,7 @@
</ComboboxOptions>
<div v-else-if="empty" :class="ui.empty.wrapper">
<Icon v-if="empty.icon" :name="empty.icon" :class="ui.empty.icon" aria-hidden="true" />
<UIcon v-if="empty.icon" :name="empty.icon" :class="ui.empty.icon" aria-hidden="true" />
<p :class="query ? ui.empty.queryLabel : ui.empty.label">
{{ query ? empty.queryLabel : empty.label }}
</p>
@@ -72,8 +72,8 @@ import { groupBy, map } from 'lodash-es'
import { defu } from 'defu'
import type { UseFuseOptions } from '@vueuse/integrations/useFuse'
import type { Group, Command } from '../../types/command-palette'
import Icon from '../elements/Icon.vue'
import Button from '../elements/Button.vue'
import UIcon from '../elements/Icon.vue'
import UButton from '../elements/Button.vue'
import type { Button as ButtonType } from '../../types/button'
import CommandPaletteGroup from './CommandPaletteGroup.vue'
import { useAppConfig } from '#imports'
@@ -88,9 +88,8 @@ export default defineComponent({
Combobox,
ComboboxInput,
ComboboxOptions,
Icon,
// eslint-disable-next-line vue/no-reserved-component-names
Button,
UIcon,
UButton,
CommandPaletteGroup
},
props: {
@@ -292,6 +291,7 @@ export default defineComponent({
ui,
// eslint-disable-next-line vue/no-dupe-keys
groups,
comboboxInput,
query,
onSelect,
onClear

View File

@@ -16,8 +16,8 @@
<div :class="[ui.group.command.base, active ? ui.group.command.active : ui.group.command.inactive, command.disabled ? 'cursor-not-allowed' : 'cursor-pointer']">
<div :class="ui.group.command.container">
<slot :name="`${group.key}-icon`" :group="group" :command="command">
<Icon v-if="command.icon" :name="command.icon" :class="[ui.group.command.icon.base, active ? ui.group.command.icon.active : ui.group.command.icon.inactive, command.iconClass]" aria-hidden="true" />
<Avatar
<UIcon v-if="command.icon" :name="command.icon" :class="[ui.group.command.icon.base, active ? ui.group.command.icon.active : ui.group.command.icon.inactive, command.iconClass]" aria-hidden="true" />
<UAvatar
v-else-if="command.avatar"
v-bind="{ size: ui.group.command.avatar.size, ...command.avatar }"
:class="ui.group.command.avatar.base"
@@ -39,13 +39,13 @@
</div>
</div>
<Icon v-if="selected" :name="selectedIcon" :class="ui.group.command.selected.icon" aria-hidden="true" />
<UIcon v-if="selected" :name="selectedIcon" :class="ui.group.command.selectedIcon.base" aria-hidden="true" />
<slot v-else-if="active && (group.active || $slots[`${group.key}-active`])" :name="`${group.key}-active`" :group="group" :command="command">
<span v-if="group.active" :class="ui.group.active">{{ group.active }}</span>
</slot>
<slot v-else :name="`${group.key}-inactive`" :group="group" :command="command">
<span v-if="command.shortcuts?.length" :class="ui.group.command.shortcuts">
<kbd v-for="shortcut of command.shortcuts" :key="shortcut" class="font-sans">{{ shortcut }}</kbd>
<UKbd v-for="shortcut of command.shortcuts" :key="shortcut">{{ shortcut }}</UKbd>
</span>
<span v-else-if="!command.disabled && group.inactive" :class="ui.group.inactive">{{ group.inactive }}</span>
</slot>
@@ -59,8 +59,9 @@
import { computed, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { ComboboxOption } from '@headlessui/vue'
import Icon from '../elements/Icon.vue'
import Avatar from '../elements/Avatar.vue'
import UIcon from '../elements/Icon.vue'
import UAvatar from '../elements/Avatar.vue'
import UKbd from '../elements/Kbd.vue'
import type { Group } from '../../types/command-palette'
// TODO: Remove
// @ts-expect-error
@@ -71,8 +72,9 @@ import appConfig from '#build/app.config'
export default defineComponent({
components: {
ComboboxOption,
Icon,
Avatar
UIcon,
UAvatar,
UKbd
},
props: {
group: {
@@ -146,3 +148,9 @@ export default defineComponent({
}
})
</script>
<style>
mark {
@apply bg-primary-400;
}
</style>

View File

@@ -1,39 +1,39 @@
<template>
<nav :class="ui.wrapper">
<LinkCustom
<ULinkCustom
v-for="(link, index) of links"
v-slot="{ isActive }"
:key="index"
v-bind="link"
:class="[ui.base, ui.spacing]"
:class="[ui.base, ui.padding]"
:active-class="ui.active"
:inactive-class="ui.inactive"
@click="link.click && link.click()"
@keyup.enter="$event.target.blur()"
>
<slot name="avatar" :link="link">
<Avatar
<UAvatar
v-if="link.avatar"
v-bind="{ size: ui.avatar.size, ...link.avatar }"
:class="[ui.avatar.base]"
/>
</slot>
<slot name="icon" :link="link" :is-active="isActive">
<Icon
<UIcon
v-if="link.icon"
:name="link.icon"
:class="[ui.icon.base, isActive ? ui.icon.active : ui.icon.inactive, link.iconClass]"
/>
</slot>
<slot :link="link">
<span v-if="link.label" class="truncate">{{ link.label }}</span>
<span v-if="link.label" :class="ui.label">{{ link.label }}</span>
</slot>
<slot name="badge" :link="link" :is-active="isActive">
<span v-if="link.badge" :class="[ui.badge.baseClass, isActive ? ui.badge.active : ui.badge.inactive]">
{{ link.badge }}
</span>
</slot>
</LinkCustom>
</ULinkCustom>
</nav>
</template>
@@ -42,9 +42,9 @@ import { computed, defineComponent } from 'vue'
import type { PropType } from 'vue'
import type { RouteLocationNormalized } from 'vue-router'
import { defu } from 'defu'
import Icon from '../elements/Icon.vue'
import Avatar from '../elements/Avatar.vue'
import LinkCustom from '../elements/LinkCustom.vue'
import UIcon from '../elements/Icon.vue'
import UAvatar from '../elements/Avatar.vue'
import ULinkCustom from '../elements/LinkCustom.vue'
import type { Avatar as AvatarType } from '../../types/avatar'
import { useAppConfig } from '#imports'
// TODO: Remove
@@ -55,9 +55,9 @@ import appConfig from '#build/app.config'
export default defineComponent({
components: {
Icon,
Avatar,
LinkCustom
UIcon,
UAvatar,
ULinkCustom
},
props: {
links: {

View File

@@ -6,7 +6,7 @@
</TransitionChild>
<div :class="ui.inner">
<div :class="[ui.container, ui.spacing]">
<div :class="[ui.container, ui.padding]">
<TransitionChild as="template" :appear="appear" v-bind="ui.transition">
<DialogPanel :class="[ui.base, ui.width, ui.height, ui.background, ui.ring, ui.rounded, ui.shadow]">
<slot />

View File

@@ -8,8 +8,8 @@
<div :class="[ui.container, ui.rounded, ui.ring]">
<div class="p-4">
<div class="flex gap-3" :class="{ 'items-start': description, 'items-center': !description }">
<Icon v-if="icon" :name="icon" :class="ui.icon" />
<Avatar v-if="avatar" v-bind="avatar" :class="ui.avatar" />
<UIcon v-if="icon" :name="icon" :class="ui.icon" />
<UAvatar v-if="avatar" v-bind="avatar" :class="ui.avatar" />
<div class="w-0 flex-1">
<p :class="ui.title">
@@ -20,15 +20,15 @@
</p>
<div v-if="description && actions.length" class="mt-3 flex items-center gap-2">
<Button v-for="(action, index) of actions" :key="index" v-bind="{ ...ui.default.action, ...action }" @click.stop="onAction(action)" />
<UButton v-for="(action, index) of actions" :key="index" v-bind="{ ...ui.default.action, ...action }" @click.stop="onAction(action)" />
</div>
</div>
<div class="flex-shrink-0 flex items-center gap-3">
<div v-if="!description && actions.length" class="flex items-center gap-2">
<Button v-for="(action, index) of actions" :key="index" v-bind="{ ...ui.default.action, ...action }" @click.stop="onAction(action)" />
<UButton v-for="(action, index) of actions" :key="index" v-bind="{ ...ui.default.action, ...action }" @click.stop="onAction(action)" />
</div>
<Button v-if="close" v-bind="{ ...ui.default.close, ...close }" @click.stop="onClose" />
<UButton v-if="close" v-bind="{ ...ui.default.close, ...close }" @click.stop="onClose" />
</div>
</div>
</div>
@@ -42,9 +42,9 @@
import { ref, computed, onMounted, onUnmounted, watchEffect, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { defu } from 'defu'
import Icon from '../elements/Icon.vue'
import Avatar from '../elements/Avatar.vue'
import Button from '../elements/Button.vue'
import UIcon from '../elements/Icon.vue'
import UAvatar from '../elements/Avatar.vue'
import UButton from '../elements/Button.vue'
import { useTimer } from '../../composables/useTimer'
import type { ToastNotificationAction } from '../../types'
import type { Avatar as AvatarType } from '../../types/avatar'
@@ -58,10 +58,9 @@ import appConfig from '#build/app.config'
export default defineComponent({
components: {
Icon,
Avatar,
// eslint-disable-next-line vue/no-reserved-component-names
Button
UIcon,
UAvatar,
UButton
},
props: {
id: {

View File

@@ -2,7 +2,7 @@
<div :class="ui.wrapper">
<div v-if="notifications.length" :class="ui.container">
<div v-for="notification of notifications" :key="notification.id">
<Notification
<UNotification
v-bind="notification"
:class="notification.click && 'cursor-pointer'"
@click="notification.click && notification.click(notification)"
@@ -14,12 +14,12 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { computed, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { defu } from 'defu'
import type { ToastNotification } from '../../types'
import { useToast } from '../../composables/useToast'
import Notification from './Notification.vue'
import UNotification from './Notification.vue'
import { useState, useAppConfig } from '#imports'
// TODO: Remove
// @ts-expect-error
@@ -29,7 +29,7 @@ import appConfig from '#build/app.config'
export default defineComponent({
components: {
Notification
UNotification
},
props: {
ui: {

View File

@@ -6,7 +6,7 @@
</TransitionChild>
<TransitionChild as="template" :appear="appear" v-bind="transitionClass">
<DialogPanel :class="[ui.base, ui.width, ui.background, ui.ring]">
<DialogPanel :class="[ui.base, ui.width, ui.background, ui.ring, ui.padding]">
<slot />
</DialogPanel>
</TransitionChild>
@@ -45,7 +45,7 @@ export default defineComponent({
},
side: {
type: String,
default: 'left',
default: 'right',
validator: (value: string) => ['left', 'right'].includes(value)
},
overlay: {

View File

@@ -12,10 +12,10 @@
</slot>
<span v-if="shortcuts?.length" :class="ui.shortcuts">
<span class="mr-1 text-gray-700 dark:text-gray-200">&middot;</span>
<kbd v-for="shortcut of shortcuts" :key="shortcut" class="flex items-center justify-center font-sans px-1 h-4 min-w-[16px] text-[10px] bg-gray-100 dark:bg-gray-800 rounded text-gray-900 dark:text-white">
<span class="mx-1 text-gray-700 dark:text-gray-200">&middot;</span>
<UKbd v-for="shortcut of shortcuts" :key="shortcut" size="xs">
{{ shortcut }}
</kbd>
</Ukbd>
</span>
</div>
</transition>
@@ -27,6 +27,7 @@
import { computed, ref, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { defu } from 'defu'
import UKbd from '../elements/Kbd.vue'
import { usePopper } from '../../composables/usePopper'
import type { PopperOptions } from '../../types'
import { useAppConfig } from '#imports'
@@ -37,6 +38,9 @@ import appConfig from '#build/app.config'
// const appConfig = useAppConfig()
export default defineComponent({
components: {
UKbd
},
props: {
text: {
type: String,

View File

@@ -1,22 +1,11 @@
import { computed } from 'vue'
import { defineNuxtPlugin, useHead, useAppConfig } from '#imports'
import { hexToRgb } from '../utils/colors'
import { defineNuxtPlugin, useHead, useAppConfig, useNuxtApp } from '#imports'
import colors from '#tailwind-config/theme/colors'
function hexToRgb (hex) {
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i
hex = hex.replace(shorthandRegex, function (_, r, g, b) {
return r + r + g + g + b + b
})
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result
? `${parseInt(result[1], 16)} ${parseInt(result[2], 16)} ${parseInt(result[3], 16)}`
: null
}
export default defineNuxtPlugin(() => {
const appConfig = useAppConfig()
const nuxtApp = useNuxtApp()
const root = computed(() => `:root {
${Object.entries(colors[appConfig.ui.primary]).map(([key, value]) => `--color-primary-${key}: ${hexToRgb(value)};`).join('\n')}
@@ -25,10 +14,25 @@ ${Object.entries(colors[appConfig.ui.gray]).map(([key, value]) => `--color-gray-
}`)
// Head
useHead({
const headData: any = {
style: [{
innerHTML: () => root.value
innerHTML: () => root.value,
tagPriority: -2
}]
})
}
// SPA mode
if (process.client && nuxtApp.isHydrating && !nuxtApp.payload.serverRendered) {
const style = document.createElement('style')
style.innerHTML = root.value
style.setAttribute('data-nuxt-ui-colors', '')
document.head.appendChild(style)
headData.script = [{
innerHTML: 'document.head.removeChild(document.querySelector(\'[data-nuxt-ui-colors]\'))'
}]
}
useHead(headData)
})

View File

@@ -3,5 +3,3 @@ export * from './clipboard'
export * from './command-palette'
export * from './popper'
export * from './toast'
export type DeepPartial<T> = Partial<{ [P in keyof T]: DeepPartial<T[P]> | { [key: string]: string } }>

View File

@@ -17,3 +17,16 @@ export const colorsToExclude = [
export const excludeColors = (colors: object) => Object.keys(omit(colors, colorsToExclude)).map(color => kebabCase(color)) as string[]
export const colorsAsRegex = (colors: string[]): string => colors.join('|')
export const hexToRgb = (hex) => {
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i
hex = hex.replace(shorthandRegex, function (_, r, g, b) {
return r + r + g + g + b + b
})
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result
? `${parseInt(result[1], 16)} ${parseInt(result[2], 16)} ${parseInt(result[3], 16)}`
: null
}

171
yarn.lock
View File

@@ -568,14 +568,14 @@
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.0.tgz#f6f729b02feee2c749f57e334b7a1b5f40a81724"
integrity sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==
"@eslint/eslintrc@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.2.tgz#01575e38707add677cf73ca1589abba8da899a02"
integrity sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==
"@eslint/eslintrc@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.3.tgz#4910db5505f4d503f27774bf356e3704818a0331"
integrity sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==
dependencies:
ajv "^6.12.4"
debug "^4.3.2"
espree "^9.5.1"
espree "^9.5.2"
globals "^13.19.0"
ignore "^5.2.0"
import-fresh "^3.2.1"
@@ -583,10 +583,10 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/js@8.39.0":
version "8.39.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.39.0.tgz#58b536bcc843f4cd1e02a7e6171da5c040f4d44b"
integrity sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==
"@eslint/js@8.40.0":
version "8.40.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.40.0.tgz#3ba73359e11f5a7bd3e407f70b3528abfae69cec"
integrity sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==
"@headlessui/vue@1.7.10":
version "1.7.10"
@@ -624,10 +624,10 @@
dependencies:
"@iconify/types" "*"
"@iconify-json/simple-icons@^1.1.51":
version "1.1.51"
resolved "https://registry.yarnpkg.com/@iconify-json/simple-icons/-/simple-icons-1.1.51.tgz#607522d64d8c82611460e259f0f0a57063307223"
integrity sha512-51iewjCewrKJXQBgV234TlpEoRtC7HZe9feJh7y3WvNtQig2ykrEKr4hCbJmHgVIKQCjtJpcZS3e2M8NlZUxyw==
"@iconify-json/simple-icons@^1.1.52":
version "1.1.52"
resolved "https://registry.yarnpkg.com/@iconify-json/simple-icons/-/simple-icons-1.1.52.tgz#cd8cb99675e0c85c07c93bda7a11c6f95808b65d"
integrity sha512-hJRenMpoG/hMbL2YzL1kOAQKVbLHE1gNckjbGwFHw3hH5CxvKssXA+3i4TVjFJnupRxmm7z4sgxjAV5HkQ5g/g==
dependencies:
"@iconify/types" "*"
@@ -846,30 +846,6 @@
unimport "^3.0.6"
untyped "^1.3.2"
"@nuxt/kit@^3.4.2":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@nuxt/kit/-/kit-3.4.2.tgz#a7ae05f154c43560f18dd56c49df1b23a2c2553b"
integrity sha512-bFUpkyG2ZF6RYqiW+tXnWssccHQQqMF4kZJJLP/0eKXf+Fkt/Is0R7IY768jy8ylnyqeMBbmpg4Zv5gSZjfZQw==
dependencies:
"@nuxt/schema" "3.4.2"
c12 "^1.4.1"
consola "^3.1.0"
defu "^6.1.2"
globby "^13.1.4"
hash-sum "^2.0.0"
ignore "^5.2.4"
jiti "^1.18.2"
knitwork "^1.0.0"
lodash.template "^4.5.0"
mlly "^1.2.0"
pathe "^1.1.0"
pkg-types "^1.0.2"
scule "^1.0.0"
semver "^7.5.0"
unctx "^2.3.0"
unimport "^3.0.6"
untyped "^1.3.2"
"@nuxt/module-builder@^0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@nuxt/module-builder/-/module-builder-0.3.1.tgz#addf633082589fa3095535e36767b7cfa6ff0a97"
@@ -914,21 +890,6 @@
unimport "^3.0.6"
untyped "^1.3.2"
"@nuxt/schema@3.4.2":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@nuxt/schema/-/schema-3.4.2.tgz#1cdafdc3246027bb9964c2ccb172003213c4d989"
integrity sha512-DXB/fyjrAssFt9KGXyS+ZSfm1A0NYKhEoc01wyz1lGo//oETzUh3MmwE6X3x65NPqDlYZ6Mnj+IdftRRophv5Q==
dependencies:
defu "^6.1.2"
hookable "^5.5.3"
pathe "^1.1.0"
pkg-types "^1.0.2"
postcss-import-resolver "^2.0.0"
std-env "^3.3.2"
ufo "^1.1.1"
unimport "^3.0.6"
untyped "^1.3.2"
"@nuxt/schema@3.4.3":
version "3.4.3"
resolved "https://registry.yarnpkg.com/@nuxt/schema/-/schema-3.4.3.tgz#106548003b7bbc40428b2af290b31b1e89acffc5"
@@ -1050,32 +1011,33 @@
eslint-plugin-vue "^9.7.0"
local-pkg "^0.4.2"
"@nuxtjs/plausible@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@nuxtjs/plausible/-/plausible-0.2.0.tgz#07a2c41c2c1cfdfb4578dd5885405a0b4c4ba397"
integrity sha512-mke+eW94PCca7zK0kNpNA9ULat5JCrBP2JUCmas6ZKpskU3+IaBv2OzWYzWJb/SsungfV9a/dukKbjAJeBTzhQ==
"@nuxtjs/plausible@^0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@nuxtjs/plausible/-/plausible-0.2.1.tgz#f1eb58334f04e93946276848eabe1feb2f5ab063"
integrity sha512-1AyV9woFeZKBhwYy05S7NBk4jeAXzWcF/SIK/S/GZ8C86G8x7f2hR2eJi9FRRQYGwHutBx2guU4tZAW4J71Azg==
dependencies:
"@nuxt/kit" "^3.0.0"
defu "^6.1.1"
pathe "^1.0.0"
"@nuxt/kit" "^3.4.3"
defu "^6.1.2"
pathe "^1.1.0"
plausible-tracker "^0.3.8"
"@nuxtjs/tailwindcss@^6.6.7":
version "6.6.7"
resolved "https://registry.yarnpkg.com/@nuxtjs/tailwindcss/-/tailwindcss-6.6.7.tgz#1217e05afd5dbd7c63d0178c3d29df1b1abec33a"
integrity sha512-WxkvOH05YURffpPBjtHavwY/nu13+u90tTj2rHdTQ3CcvWcnKZ9e2q7NjIrz5sopkuRYNGtKnSZa3eA9iTk0Og==
"@nuxtjs/tailwindcss@^6.6.8":
version "6.6.8"
resolved "https://registry.yarnpkg.com/@nuxtjs/tailwindcss/-/tailwindcss-6.6.8.tgz#d1d63487b43b9c011465942c044e0269a3c9ce05"
integrity sha512-U0wnBLFStrhpY1pqFOdro8AptXDsSFFjYEF5Rp306dbDlDs6ukZrnoDZQOAor/x+w12S7lUdQUN3j8L5YuXyKQ==
dependencies:
"@nuxt/kit" "^3.4.2"
"@nuxt/kit" "^3.4.3"
"@nuxt/postcss8" "^1.1.3"
autoprefixer "^10.4.14"
chokidar "^3.5.3"
clear-module "^4.1.2"
colorette "^2.0.20"
cookie-es "^0.5.0"
cookie-es "^1.0.0"
defu "^6.1.2"
destr "^1.2.2"
h3 "^1.6.4"
h3 "^1.6.5"
iron-webcrypto "^0.7.0"
minimatch "^9.0.0"
pathe "^1.1.0"
postcss "^8.4.23"
postcss-custom-properties "^13.1.5"
@@ -1083,7 +1045,7 @@
radix3 "^1.0.1"
tailwind-config-viewer "^1.7.2"
tailwindcss "~3.3.2"
ufo "^1.1.1"
ufo "^1.1.2"
uncrypto "^0.1.2"
"@pkgr/utils@^2.3.1":
@@ -1199,10 +1161,10 @@
resolved "https://registry.yarnpkg.com/@tailwindcss/aspect-ratio/-/aspect-ratio-0.4.2.tgz#9ffd52fee8e3c8b20623ff0dcb29e5c21fb0a9ba"
integrity sha512-8QPrypskfBa7QIMuKHg2TA7BqES6vhBrDLOv8Unb6FcFyd3TjKbc6lcmb9UPQHxfl24sXoJ41ux/H7qQQvfaSQ==
"@tailwindcss/forms@^0.0.0-insiders.615a228":
version "0.0.0-insiders.e8e01c3"
resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.0.0-insiders.e8e01c3.tgz#922fbfe585373109a64a3fa9d181c735b2d823ec"
integrity sha512-Nc+oHhNpqFZMl1AlOarIR31I38Y5kLlLrRfnOPrRFAHX77gwKuXid3DwaNvVye0fyaGzcAfTmWWz72NewKAEdQ==
"@tailwindcss/forms@^0.5.3":
version "0.5.3"
resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.5.3.tgz#e4d7989686cbcaf416c53f1523df5225332a86e7"
integrity sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==
dependencies:
mini-svg-data-uri "^1.2.3"
@@ -1279,10 +1241,10 @@
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
"@types/node@^18.16.3":
version "18.16.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.3.tgz#6bda7819aae6ea0b386ebc5b24bdf602f1b42b01"
integrity sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==
"@types/node@^20.1.2":
version "20.1.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.1.2.tgz#8fd63447e3f99aba6c3168fd2ec4580d5b97886f"
integrity sha512-CTO/wa8x+rZU626cL2BlbCDzydgnFNgc19h4YvizpTO88MFQxab8wqisxaofQJ/9bLGugRdWIuX/TbIs6VVF6g==
"@types/normalize-package-data@^2.4.0":
version "2.4.1"
@@ -2708,6 +2670,11 @@ cookie-es@^0.5.0:
resolved "https://registry.yarnpkg.com/cookie-es/-/cookie-es-0.5.0.tgz#a6ad89923e68c542fc9e760b07aefa5ab020d719"
integrity sha512-RyZrFi6PNpBFbIaQjXDlFIhFVqV42QeKSZX1yQIl6ihImq6vcHNGMtqQ/QzY3RMPuYSkvsRwtnt5M9NeYxKt0g==
cookie-es@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/cookie-es/-/cookie-es-1.0.0.tgz#4759684af168dfc54365b2c2dda0a8d7ee1e4865"
integrity sha512-mWYvfOLrfEc996hlKcdABeIiPHUPC6DM2QYZdGGOvhOTbA3tjm2eBwqlJpoFdjC89NI4Qt6h0Pu06Mp+1Pj5OQ==
cookies@~0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.8.0.tgz#1293ce4b391740a8406e3c9870e828c4b54f3f90"
@@ -2983,7 +2950,7 @@ defu@^3.2.2:
resolved "https://registry.yarnpkg.com/defu/-/defu-3.2.2.tgz#be20f4cc49b9805d54ee6b610658d53894942e97"
integrity sha512-8UWj5lNv7HD+kB0e9w77Z7TdQlbUYDVWqITLHNqFIn6khrNHv5WQo38Dcm1f6HeNyZf0U7UbPf6WeZDSdCzGDQ==
defu@^6.0.0, defu@^6.1.1, defu@^6.1.2:
defu@^6.0.0, defu@^6.1.2:
version "6.1.2"
resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.2.tgz#1217cba167410a1765ba93893c6dbac9ed9d9e5c"
integrity sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==
@@ -3572,15 +3539,20 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc"
integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==
eslint@^8.39.0:
version "8.39.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.39.0.tgz#7fd20a295ef92d43809e914b70c39fd5a23cf3f1"
integrity sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==
eslint-visitor-keys@^3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994"
integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==
eslint@^8.40.0:
version "8.40.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.40.0.tgz#a564cd0099f38542c4e9a2f630fa45bf33bc42a4"
integrity sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==
dependencies:
"@eslint-community/eslint-utils" "^4.2.0"
"@eslint-community/regexpp" "^4.4.0"
"@eslint/eslintrc" "^2.0.2"
"@eslint/js" "8.39.0"
"@eslint/eslintrc" "^2.0.3"
"@eslint/js" "8.40.0"
"@humanwhocodes/config-array" "^0.11.8"
"@humanwhocodes/module-importer" "^1.0.1"
"@nodelib/fs.walk" "^1.2.8"
@@ -3591,8 +3563,8 @@ eslint@^8.39.0:
doctrine "^3.0.0"
escape-string-regexp "^4.0.0"
eslint-scope "^7.2.0"
eslint-visitor-keys "^3.4.0"
espree "^9.5.1"
eslint-visitor-keys "^3.4.1"
espree "^9.5.2"
esquery "^1.4.2"
esutils "^2.0.2"
fast-deep-equal "^3.1.3"
@@ -3618,7 +3590,7 @@ eslint@^8.39.0:
strip-json-comments "^3.1.0"
text-table "^0.2.0"
espree@^9.3.1, espree@^9.5.1:
espree@^9.3.1:
version "9.5.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.1.tgz#4f26a4d5f18905bf4f2e0bd99002aab807e96dd4"
integrity sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==
@@ -3627,6 +3599,15 @@ espree@^9.3.1, espree@^9.5.1:
acorn-jsx "^5.3.2"
eslint-visitor-keys "^3.4.0"
espree@^9.5.2:
version "9.5.2"
resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.2.tgz#e994e7dc33a082a7a82dceaf12883a829353215b"
integrity sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==
dependencies:
acorn "^8.8.0"
acorn-jsx "^5.3.2"
eslint-visitor-keys "^3.4.1"
esquery@^1.4.0, esquery@^1.4.2:
version "1.5.0"
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b"
@@ -4236,6 +4217,19 @@ h3@^1.6.4:
ufo "^1.1.1"
uncrypto "^0.1.2"
h3@^1.6.5:
version "1.6.5"
resolved "https://registry.yarnpkg.com/h3/-/h3-1.6.5.tgz#0836ffb87aa4a557871e02bdcf6116f41f7240fc"
integrity sha512-A0r2LCDzeavSfcAbJpMwHXLcSN0H3FpEZtYigbz4IYun0fOE8r6vy3galwubAnmc6gXVob4261zXQsu3sZayzg==
dependencies:
cookie-es "^1.0.0"
defu "^6.1.2"
destr "^1.2.2"
iron-webcrypto "^0.7.0"
radix3 "^1.0.1"
ufo "^1.1.2"
uncrypto "^0.1.2"
handlebars@^4.7.7:
version "4.7.7"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
@@ -8307,6 +8301,11 @@ ufo@^1.0.0, ufo@^1.1.0, ufo@^1.1.1:
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.1.1.tgz#e70265e7152f3aba425bd013d150b2cdf4056d7c"
integrity sha512-MvlCc4GHrmZdAllBc0iUDowff36Q9Ndw/UzqmEKyrfSzokTd9ZCy1i+IIk5hrYKkjoYVQyNbrw7/F8XJ2rEwTg==
ufo@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.1.2.tgz#d0d9e0fa09dece0c31ffd57bd363f030a35cfe76"
integrity sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==
uglify-js@^3.1.4:
version "3.17.4"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c"
@@ -8736,7 +8735,7 @@ vue-template-compiler@^2.7.14:
de-indent "^1.0.2"
he "^1.2.0"
vue-tsc@^1.6.3:
vue-tsc@1.6.3:
version "1.6.3"
resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-1.6.3.tgz#db49f0060c595ec3123d66086274b891d7e6282f"
integrity sha512-q7l27j0eSJgyGat0khetrvoeaAHieRZFnf8WAJyKvB3eF0AxmLqfs4ahwZhaojBJjZ/lAXZa+Xt8EX54KzQ34w==