mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-15 20:48:12 +01:00
Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3558eb1a4f | ||
|
|
1c4d46e056 | ||
|
|
1b0ed9e732 | ||
|
|
b72037a777 | ||
|
|
a7644860b8 | ||
|
|
c90cd9c4f3 | ||
|
|
7805168685 | ||
|
|
27717a55b3 | ||
|
|
d651a22dce | ||
|
|
c3ecbf4b20 | ||
|
|
d8b10f3eef | ||
|
|
1071b80b39 | ||
|
|
c5e9a1ef46 | ||
|
|
afe69a570d | ||
|
|
e6ed834cea | ||
|
|
30c5412a6b | ||
|
|
01f56d9553 | ||
|
|
91f273c117 | ||
|
|
cda8ce32a3 | ||
|
|
2bc0eb05d1 | ||
|
|
cfc4bdfbfe | ||
|
|
370d05921d | ||
|
|
b6455a151d | ||
|
|
8c0e0ec823 | ||
|
|
4f56921096 | ||
|
|
6a5ee32e05 | ||
|
|
4ea07e1077 | ||
|
|
5fd65d0917 | ||
|
|
2ec0cee1d9 | ||
|
|
758e6f1400 | ||
|
|
275fa1831d | ||
|
|
8b5e08f6f2 | ||
|
|
1635f57de6 | ||
|
|
b3e0122001 | ||
|
|
4f9d20e603 | ||
|
|
f7add47cf2 | ||
|
|
99c1c683eb | ||
|
|
ff9f6c251d | ||
|
|
e0c703ca6c | ||
|
|
2210faa160 | ||
|
|
19589b5e05 | ||
|
|
7051fa39a7 | ||
|
|
911278e95e | ||
|
|
c015148f29 | ||
|
|
cb1fd55801 | ||
|
|
16fd1c0ca3 | ||
|
|
84ac92ed7a | ||
|
|
0ade69de26 | ||
|
|
d9193abf55 | ||
|
|
44a78d7f67 | ||
|
|
3ce600f89a | ||
|
|
662dd9ee65 | ||
|
|
3be3751bfc | ||
|
|
5042b5806b | ||
|
|
2535301bcd | ||
|
|
629bb72424 | ||
|
|
543b4e025f | ||
|
|
7f18c6bdc7 | ||
|
|
0ea924ca0d | ||
|
|
22f3d42470 | ||
|
|
1ad96065fd | ||
|
|
952786ed79 | ||
|
|
9cf92e34ab | ||
|
|
6321d3ed8d | ||
|
|
bc0c168c0b | ||
|
|
9975305f2a | ||
|
|
edc1bd677b | ||
|
|
32d1f21299 | ||
|
|
f901e417e1 | ||
|
|
23921e1ae2 | ||
|
|
8a5f52dbc8 | ||
|
|
04d5ce1c51 | ||
|
|
3a300f72c1 | ||
|
|
927debfb6b | ||
|
|
44176ee897 | ||
|
|
7f9d69183c | ||
|
|
23e5f4c501 | ||
|
|
24998e3ac5 | ||
|
|
bebf18a89a | ||
|
|
1b56b50d4d | ||
|
|
3337559b89 | ||
|
|
d33a23ebc0 | ||
|
|
28586c35a5 | ||
|
|
e521e1ac24 | ||
|
|
8ea3223071 | ||
|
|
4cbe983f61 | ||
|
|
e42969f003 | ||
|
|
c93e37a0eb | ||
|
|
9e20f96b65 | ||
|
|
9f6f132a76 | ||
|
|
5517cc2846 | ||
|
|
8c24b5dc97 | ||
|
|
ec8bd5cdc4 | ||
|
|
6fab68baa8 | ||
|
|
fc951e2980 | ||
|
|
08ff6e6c09 | ||
|
|
9dcdaf474e | ||
|
|
e9f0224b91 | ||
|
|
929192fd46 |
3
.github/workflows/ci-dev.yml
vendored
3
.github/workflows/ci-dev.yml
vendored
@@ -41,6 +41,9 @@ jobs:
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
|
||||
- name: Typecheck
|
||||
run: yarn typecheck
|
||||
|
||||
- name: Build
|
||||
run: yarn build
|
||||
|
||||
|
||||
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -41,6 +41,9 @@ jobs:
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
|
||||
- name: Typecheck
|
||||
run: yarn typecheck
|
||||
|
||||
- name: Build
|
||||
run: yarn build
|
||||
|
||||
|
||||
153
CHANGELOG.md
153
CHANGELOG.md
@@ -2,6 +2,159 @@
|
||||
|
||||
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.
|
||||
|
||||
### [0.1.35](https://github.com/nuxtlabs/ui/compare/v0.1.34...v0.1.35) (2023-02-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **AvatarGroup:** preset size prop ([c90cd9c](https://github.com/nuxtlabs/ui/commit/c90cd9c4f37bc3ce5f6e13f3279dc2c574c76524))
|
||||
* **Dropdown:** lint ([1c4d46e](https://github.com/nuxtlabs/ui/commit/1c4d46e056adf84d69462a12af8ac29f93cbf87a))
|
||||
* **Dropdown:** prevent panel display when no items ([a764486](https://github.com/nuxtlabs/ui/commit/a7644860b8c22a0163e01ca2c0eab2c48b09745a))
|
||||
|
||||
### [0.1.34](https://github.com/nuxtlabs/ui/compare/v0.1.33...v0.1.34) (2023-01-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **CommandPalette:** typecheck ([27717a5](https://github.com/nuxtlabs/ui/commit/27717a55b3e5120f32fba2bcea30f5a91262f1c5))
|
||||
|
||||
### [0.1.33](https://github.com/nuxtlabs/ui/compare/v0.1.32...v0.1.33) (2023-01-27)
|
||||
|
||||
### [0.1.32](https://github.com/nuxtlabs/ui/compare/v0.1.31...v0.1.32) (2023-01-23)
|
||||
|
||||
### [0.1.31](https://github.com/nuxtlabs/ui/compare/v0.1.30...v0.1.31) (2023-01-17)
|
||||
|
||||
### [0.1.30](https://github.com/nuxtlabs/ui/compare/v0.1.29...v0.1.30) (2023-01-17)
|
||||
|
||||
### [0.1.29](https://github.com/nuxtlabs/ui/compare/v0.1.28...v0.1.29) (2023-01-17)
|
||||
|
||||
### [0.1.28](https://github.com/nuxtlabs/ui/compare/v0.1.27...v0.1.28) (2023-01-13)
|
||||
|
||||
### [0.1.27](https://github.com/nuxtlabs/ui/compare/v0.1.26...v0.1.27) (2023-01-12)
|
||||
|
||||
### [0.1.26](https://github.com/nuxtlabs/ui/compare/v0.1.25...v0.1.26) (2023-01-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **CommandPalette:** select first item on search changes ([#126](https://github.com/nuxtlabs/ui/issues/126)) ([4f56921](https://github.com/nuxtlabs/ui/commit/4f56921096f5885cdab8b7cb5c5aa01304188e11))
|
||||
|
||||
### [0.1.25](https://github.com/nuxtlabs/ui/compare/v0.1.24...v0.1.25) (2023-01-09)
|
||||
|
||||
### [0.1.24](https://github.com/nuxtlabs/ui/compare/v0.1.23...v0.1.24) (2023-01-04)
|
||||
|
||||
### [0.1.23](https://github.com/nuxtlabs/ui/compare/v0.1.22...v0.1.23) (2022-12-20)
|
||||
|
||||
### [0.1.22](https://github.com/nuxtlabs/ui/compare/v0.1.21...v0.1.22) (2022-12-19)
|
||||
|
||||
### [0.1.21](https://github.com/nuxtlabs/ui/compare/v0.1.20...v0.1.21) (2022-12-19)
|
||||
|
||||
### [0.1.20](https://github.com/nuxtlabs/ui/compare/v0.1.19...v0.1.20) (2022-12-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* avoid referring to complex types in props ([#123](https://github.com/nuxtlabs/ui/issues/123)) ([ff9f6c2](https://github.com/nuxtlabs/ui/commit/ff9f6c251df59641862d82587e5d963c8e6ea298))
|
||||
|
||||
### [0.1.19](https://github.com/nuxtlabs/ui/compare/v0.1.18...v0.1.19) (2022-12-16)
|
||||
|
||||
### [0.1.18](https://github.com/nuxtlabs/ui/compare/v0.1.17...v0.1.18) (2022-12-15)
|
||||
|
||||
### [0.1.17](https://github.com/nuxtlabs/ui/compare/v0.1.16...v0.1.17) (2022-12-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove stop propagation on mode hover ([16fd1c0](https://github.com/nuxtlabs/ui/commit/16fd1c0ca38f1438e791c0d44399f590d9f20d02))
|
||||
|
||||
### [0.1.16](https://github.com/nuxtlabs/ui/compare/v0.1.15...v0.1.16) (2022-12-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Popover:** preset from tooltip ([0ade69d](https://github.com/nuxtlabs/ui/commit/0ade69de2689b094b11a2dead8f71e3d2dccd552))
|
||||
|
||||
### [0.1.15](https://github.com/nuxtlabs/ui/compare/v0.1.14...v0.1.15) (2022-12-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Dropdown:** better handle item click to preventDefault ([#119](https://github.com/nuxtlabs/ui/issues/119)) ([44a78d7](https://github.com/nuxtlabs/ui/commit/44a78d7f679812c59d4410d0bbc01f09abaa78dd))
|
||||
|
||||
### [0.1.14](https://github.com/nuxtlabs/ui/compare/v0.1.13...v0.1.14) (2022-12-02)
|
||||
|
||||
### [0.1.13](https://github.com/nuxtlabs/ui/compare/v0.1.12...v0.1.13) (2022-12-01)
|
||||
|
||||
### [0.1.12](https://github.com/nuxtlabs/ui/compare/v0.1.11...v0.1.12) (2022-11-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Checkbox:** types ([629bb72](https://github.com/nuxtlabs/ui/commit/629bb724249cfe1fdd999cf52f8e72ca444bd94d))
|
||||
|
||||
### [0.1.11](https://github.com/nuxtlabs/ui/compare/v0.1.10...v0.1.11) (2022-11-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Checkbox:** revert type fix as it breaks checkboxes ([7f18c6b](https://github.com/nuxtlabs/ui/commit/7f18c6bdc7c0054b2e5d4f9bf4e362847a3ba3a3))
|
||||
|
||||
### [0.1.10](https://github.com/nuxtlabs/ui/compare/v0.1.9...v0.1.10) (2022-11-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* default popper options ([1ad9606](https://github.com/nuxtlabs/ui/commit/1ad96065fd706d828b906db3a5d578226ff08c36))
|
||||
* default props for command palette ([#116](https://github.com/nuxtlabs/ui/issues/116)) ([952786e](https://github.com/nuxtlabs/ui/commit/952786ed79cd9cdf523f6eac5958f68790bacbea))
|
||||
|
||||
### [0.1.9](https://github.com/nuxtlabs/ui/compare/v0.1.8...v0.1.9) (2022-11-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Icon:** couldn't load anymore ([6321d3e](https://github.com/nuxtlabs/ui/commit/6321d3ed8d5c9478cb1dafc1da94b21d0c7edb88))
|
||||
* **Icon:** eslint ignore ([bc0c168](https://github.com/nuxtlabs/ui/commit/bc0c168c0b0feae96d6a1848c3a356d846e2cbb5))
|
||||
|
||||
### [0.1.8](https://github.com/nuxtlabs/ui/compare/v0.1.7...v0.1.8) (2022-11-16)
|
||||
|
||||
### [0.1.7](https://github.com/nuxtlabs/ui/compare/v0.1.6...v0.1.7) (2022-11-16)
|
||||
|
||||
### [0.1.6](https://github.com/nuxtlabs/ui/compare/v0.1.5...v0.1.6) (2022-11-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **SelectCustom:** add `w-full` on `ComboboxButton` ([3a300f7](https://github.com/nuxtlabs/ui/commit/3a300f72c1eca756ffd8f07ab871bf9c7bd7868d))
|
||||
|
||||
### [0.1.5](https://github.com/nuxtlabs/ui/compare/v0.1.4...v0.1.5) (2022-11-08)
|
||||
|
||||
### [0.1.4](https://github.com/nuxtlabs/ui/compare/v0.1.3...v0.1.4) (2022-11-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **button:** support `RouteLocationRaw` type for `to` ([#112](https://github.com/nuxtlabs/ui/issues/112)) ([1b56b50](https://github.com/nuxtlabs/ui/commit/1b56b50d4d54a5cb9e5febacdf42867988ae6c5d))
|
||||
|
||||
### [0.1.3](https://github.com/nuxtlabs/ui/compare/v0.1.2...v0.1.3) (2022-11-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docs:** component input string field ([e521e1a](https://github.com/nuxtlabs/ui/commit/e521e1ac2421dc331652f1ea4ac2b0b2959dc069))
|
||||
* **types:** add missing field in command palette type ([#111](https://github.com/nuxtlabs/ui/issues/111)) ([28586c3](https://github.com/nuxtlabs/ui/commit/28586c35a558d9e925094f86e07acdb928d54ad7))
|
||||
|
||||
### [0.1.2](https://github.com/nuxtlabs/ui/compare/v0.1.1...v0.1.2) (2022-10-27)
|
||||
|
||||
### [0.1.1](https://github.com/nuxtlabs/ui/compare/v0.1.0...v0.1.1) (2022-10-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **CommandPalette:** lint ([6fab68b](https://github.com/nuxtlabs/ui/commit/6fab68baa836c97680446e8cfdee7c5a64008539))
|
||||
* **Dropdown:** close on click item with `to` ([#103](https://github.com/nuxtlabs/ui/issues/103)) ([5517cc2](https://github.com/nuxtlabs/ui/commit/5517cc28467f957956a20126c1ce48e64677cb82))
|
||||
* **Popover:** avoid crash on mount if ref not loaded ([#105](https://github.com/nuxtlabs/ui/issues/105)) ([e9f0224](https://github.com/nuxtlabs/ui/commit/e9f0224b919445260d3b19511420a3fd448e4ea7))
|
||||
* **SelectCustom:** types and lint ([ec8bd5c](https://github.com/nuxtlabs/ui/commit/ec8bd5cdc49feb924dfdff352d9f3d788653c583))
|
||||
|
||||
## [0.1.0](https://github.com/nuxtlabs/ui/compare/v0.0.3...v0.1.0) (2022-10-25)
|
||||
|
||||
|
||||
|
||||
12
build.config.ts
Normal file
12
build.config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { defineBuildConfig } from 'unbuild'
|
||||
|
||||
export default defineBuildConfig({
|
||||
hooks: {
|
||||
'rollup:done': async (ctx) => {
|
||||
// copy env.d.ts to dist
|
||||
await fs.copyFile(resolve('src/env.d.ts'), join(ctx.options.outDir, 'env.d.ts'))
|
||||
}
|
||||
}
|
||||
})
|
||||
15
docs/app.vue
15
docs/app.vue
@@ -10,7 +10,9 @@
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<UButton variant="transparent" :icon="colorMode.value === 'dark' ? 'heroicons-outline:moon' : 'heroicons-outline:sun'" @click="toggleDark" />
|
||||
<ColorScheme placeholder="" tag="span">
|
||||
<UButton variant="transparent" :icon="colorMode.value === 'dark' ? 'heroicons-outline:moon' : 'heroicons-outline:sun'" @click="toggleDark" />
|
||||
</ColorScheme>
|
||||
<UButton to="https://github.com/nuxtlabs/ui" target="_blank" variant="transparent" icon="fa-brands:github" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -19,7 +21,7 @@
|
||||
|
||||
<UContainer class="mt-16">
|
||||
<div class="lg:grid lg:grid-cols-10 lg:gap-10 lg:relative">
|
||||
<aside class="lg:flex lg:flex-col lg:relative pb-8 lg:pb-0 lg:sticky lg:top-0 px-4 sm:px-6 lg:px-0 lg:pt-16 lg:-mt-16 lg:self-start lg:col-span-2 lg:overflow-hidden lg:sticky lg:h-screen">
|
||||
<aside class="lg:flex lg:flex-col pb-8 lg:pb-0 lg:sticky lg:top-0 px-4 sm:px-6 lg:px-0 lg:pt-16 lg:-mt-16 lg:self-start lg:col-span-2 lg:overflow-hidden lg:h-screen">
|
||||
<nav class="overflow-y-auto h-auto pt-8 lg:py-12">
|
||||
<ul class="space-y-6">
|
||||
<li v-for="section of sections" :key="section">
|
||||
@@ -57,7 +59,14 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
useMeta({
|
||||
useHead({
|
||||
title: '@nuxthq/ui',
|
||||
meta: [
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' }
|
||||
],
|
||||
link: [
|
||||
{ rel: 'stylesheet', href: 'https://rsms.me/inter/inter.css' }
|
||||
],
|
||||
bodyAttrs: {
|
||||
class: 'antialiased font-sans text-gray-700 bg-gray-50 dark:bg-gray-900 dark:text-gray-200 bg-white dark:bg-black'
|
||||
}
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
import { defineNuxtConfig } from 'nuxt'
|
||||
import defaultTheme from 'tailwindcss/defaultTheme'
|
||||
import nuxtUI from '../src/module'
|
||||
|
||||
// https://v3.nuxtjs.org/docs/directory-structure/nuxt.config
|
||||
export default defineNuxtConfig({
|
||||
meta: {
|
||||
title: '@nuxthq/ui',
|
||||
meta: [
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' }
|
||||
],
|
||||
link: [
|
||||
{ rel: 'stylesheet', href: 'https://rsms.me/inter/inter.css' }
|
||||
]
|
||||
},
|
||||
modules: [
|
||||
nuxtUI
|
||||
],
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable vue/no-template-shadow -->
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<!-- eslint-disable vue/no-v-text-v-html-on-component -->
|
||||
<template>
|
||||
@@ -66,7 +67,7 @@
|
||||
size="sm"
|
||||
/>
|
||||
<UInput
|
||||
v-else-if="prop.type === 'String' && typeof prop.value === 'string'"
|
||||
v-else-if="prop.type === 'String'"
|
||||
v-model="prop.value"
|
||||
:name="prop.key"
|
||||
size="sm"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div class="pb-10 border-b u-border-gray-200 mb-10">
|
||||
@@ -95,7 +96,7 @@
|
||||
Popover:
|
||||
</div>
|
||||
|
||||
<UPopover mode="hover" wrapper-class="inline-block relative">
|
||||
<UPopover wrapper-class="inline-block relative">
|
||||
<template #default="{ open }">
|
||||
<UButton variant="secondary" :icon="open ? 'heroicons-solid:chevron-up' : 'heroicons-solid:chevron-down'" trailing icon-class="transition">
|
||||
Open popover!
|
||||
@@ -103,46 +104,8 @@
|
||||
</template>
|
||||
|
||||
<template #panel>
|
||||
<div
|
||||
class="overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5"
|
||||
>
|
||||
<div class="relative grid gap-8 bg-white p-7">
|
||||
<a
|
||||
v-for="item in solutions"
|
||||
:key="item.name"
|
||||
:href="item.href"
|
||||
class="flex items-center p-2 -m-3 transition duration-150 ease-in-out rounded-lg hover:bg-gray-50 focus:outline-none focus-visible:ring focus-visible:ring-orange-500 focus-visible:ring-opacity-50"
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-center flex-shrink-0 w-10 h-10 text-white sm:h-12 sm:w-12"
|
||||
>
|
||||
<div v-html="item.icon" />
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-medium u-text-gray-900">
|
||||
{{ item.name }}
|
||||
</p>
|
||||
<p class="text-sm u-text-gray-500">
|
||||
{{ item.description }}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-50">
|
||||
<a
|
||||
href="##"
|
||||
class="flow-root px-2 py-2 transition duration-150 ease-in-out rounded-md hover:bg-gray-100 focus:outline-none focus-visible:ring focus-visible:ring-orange-500 focus-visible:ring-opacity-50"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
<span class="text-sm font-medium u-text-gray-900">
|
||||
Documentation
|
||||
</span>
|
||||
</span>
|
||||
<span class="block text-sm u-text-gray-500">
|
||||
Start integrating products and tools
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="p-4 u-bg-white border u-border-gray-200 rounded-md">
|
||||
Panel
|
||||
</div>
|
||||
</template>
|
||||
</UPopover>
|
||||
@@ -153,8 +116,8 @@
|
||||
Tooltip:
|
||||
</div>
|
||||
|
||||
<UTooltip text="Hello tooltip!">
|
||||
<UIcon name="heroicons-outline:information-circle" class="w-6 h-6 text-black cursor-pointer" />
|
||||
<UTooltip text="Hello tooltip!" :shortcuts="['⌘', 'G']">
|
||||
<UIcon name="heroicons-outline:information-circle" class="w-6 h-6 u-text-gray-900 cursor-pointer" />
|
||||
</UTooltip>
|
||||
</div>
|
||||
|
||||
@@ -377,95 +340,6 @@ const customDropdownItems = [
|
||||
}]
|
||||
]
|
||||
|
||||
const solutions = [
|
||||
{
|
||||
name: 'Insights',
|
||||
description: 'Measure actions your users take',
|
||||
href: '##',
|
||||
icon: `
|
||||
<svg
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 48 48"
|
||||
fill="none"
|
||||
aria-hidden='true'
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="48" height="48" rx="8" fill="#FFEDD5" />
|
||||
<path
|
||||
d="M24 11L35.2583 17.5V30.5L24 37L12.7417 30.5V17.5L24 11Z"
|
||||
stroke="#FB923C"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M16.7417 19.8094V28.1906L24 32.3812L31.2584 28.1906V19.8094L24 15.6188L16.7417 19.8094Z"
|
||||
stroke="#FDBA74"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M20.7417 22.1196V25.882L24 27.7632L27.2584 25.882V22.1196L24 20.2384L20.7417 22.1196Z"
|
||||
stroke="#FDBA74"
|
||||
stroke-width="2"
|
||||
/>
|
||||
</svg>
|
||||
`
|
||||
},
|
||||
{
|
||||
name: 'Automations',
|
||||
description: 'Create your own targeted content',
|
||||
href: '##',
|
||||
icon: `
|
||||
<svg
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 48 48"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="48" height="48" rx="8" fill="#FFEDD5" />
|
||||
<path
|
||||
d="M28.0413 20L23.9998 13L19.9585 20M32.0828 27.0001L36.1242 34H28.0415M19.9585 34H11.8755L15.9171 27"
|
||||
stroke="#FB923C"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M18.804 30H29.1963L24.0001 21L18.804 30Z"
|
||||
stroke="#FDBA74"
|
||||
stroke-width="2"
|
||||
/>
|
||||
</svg>
|
||||
`
|
||||
},
|
||||
{
|
||||
name: 'Reports',
|
||||
description: 'Keep track of your growth',
|
||||
href: '##',
|
||||
icon: `
|
||||
<svg
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 48 48"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="48" height="48" rx="8" fill="#FFEDD5" />
|
||||
<rect x="13" y="32" width="2" height="4" fill="#FDBA74" />
|
||||
<rect x="17" y="28" width="2" height="8" fill="#FDBA74" />
|
||||
<rect x="21" y="24" width="2" height="12" fill="#FDBA74" />
|
||||
<rect x="25" y="20" width="2" height="16" fill="#FDBA74" />
|
||||
<rect x="29" y="16" width="2" height="20" fill="#FB923C" />
|
||||
<rect x="33" y="12" width="2" height="24" fill="#FB923C" />
|
||||
</svg>
|
||||
`
|
||||
}
|
||||
]
|
||||
|
||||
const onNotificationClick = () => {
|
||||
$toast.error({ title: 'Error', description: 'This is an error message' })
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div class="pb-10 border-b u-border-gray-200 mb-10">
|
||||
|
||||
34
package.json
34
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nuxthq/ui",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.35",
|
||||
"repository": "https://github.com/nuxtlabs/ui",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
@@ -20,35 +20,37 @@
|
||||
"dev": "nuxi dev docs",
|
||||
"build:docs": "nuxi generate docs",
|
||||
"lint": "eslint --ext .ts,.js,.vue .",
|
||||
"typecheck": "nuxi typecheck",
|
||||
"prepare": "nuxi prepare docs",
|
||||
"release": "yarn lint && standard-version && git push --follow-tags"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/vue": "^1.7.3",
|
||||
"@iconify/vue": "^4.0.0",
|
||||
"@nuxt/kit": "^3.0.0-rc.12",
|
||||
"@nuxtjs/color-mode": "^3.1.8",
|
||||
"@nuxtjs/tailwindcss": "^6.1.1",
|
||||
"@headlessui/vue": "1.7.4",
|
||||
"@iconify/vue": "^4.0.2",
|
||||
"@nuxt/kit": "^3.0.0",
|
||||
"@nuxtjs/color-mode": "^3.2.0",
|
||||
"@nuxtjs/tailwindcss": "^6.2.0",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/line-clamp": "^0.4.2",
|
||||
"@tailwindcss/typography": "^0.5.7",
|
||||
"@vueuse/core": "^9.3.1",
|
||||
"@vueuse/integrations": "^9.3.1",
|
||||
"defu": "^6.1.0",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@vueuse/core": "^9.11.1",
|
||||
"@vueuse/integrations": "^9.11.1",
|
||||
"defu": "^6.1.1",
|
||||
"fuse.js": "^6.6.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"tailwindcss": "^3.2.1"
|
||||
"tailwindcss": "^3.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/module-builder": "^0.2.0",
|
||||
"@nuxtjs/eslint-config-typescript": "^11.0.0",
|
||||
"@nuxt/module-builder": "^0.2.1",
|
||||
"@nuxtjs/eslint-config-typescript": "^12.0.0",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"eslint": "^8.26.0",
|
||||
"nuxt": "^3.0.0-rc.9",
|
||||
"eslint": "^8.32.0",
|
||||
"nuxt": "^3.0.0",
|
||||
"standard-version": "^9.5.0",
|
||||
"vue-tsc": "^1.0.9"
|
||||
"unbuild": "^1.1.1",
|
||||
"vue-tsc": "^1.0.24"
|
||||
},
|
||||
"build": {
|
||||
"externals": [
|
||||
|
||||
6
src/env.d.ts
vendored
Normal file
6
src/env.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { DefaultPreset } from './runtime/presets/default'
|
||||
|
||||
declare module '#build/ui' {
|
||||
declare const preset: DefaultPreset
|
||||
export default preset
|
||||
}
|
||||
@@ -114,6 +114,11 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
filename: 'ui.mjs',
|
||||
getContents: () => `export default ${JSON.stringify(ui)}`
|
||||
})
|
||||
addTemplate({
|
||||
filename: 'ui.d.ts',
|
||||
write: true,
|
||||
getContents: () => 'declare const d: any; export default d;'
|
||||
})
|
||||
})
|
||||
|
||||
await installModule('@nuxtjs/color-mode', { classSuffix: '' })
|
||||
|
||||
@@ -31,7 +31,7 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: 'md',
|
||||
validator (value: string) {
|
||||
return ['xxxs', 'xxs', 'xs', 'sm', 'md', 'lg', 'xl'].includes(value)
|
||||
return Object.keys($ui.avatar.size).includes(value)
|
||||
}
|
||||
},
|
||||
max: {
|
||||
|
||||
@@ -6,16 +6,21 @@
|
||||
:aria-label="ariaLabel"
|
||||
v-bind="buttonProps"
|
||||
>
|
||||
<Icon v-if="isLeading" :name="leadingIconName" :class="leadingIconClass" aria-hidden="true" />
|
||||
<slot><span :class="truncate ? 'text-left break-all line-clamp-1' : ''">{{ label }}</span></slot>
|
||||
<Icon v-if="isTrailing" :name="trailingIconName" :class="trailingIconClass" aria-hidden="true" />
|
||||
<Icon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="leadingIconClass" aria-hidden="true" />
|
||||
<slot>
|
||||
<span :class="[truncate ? 'text-left break-all line-clamp-1' : '', compact ? 'hidden sm:block' : '']">
|
||||
<span :class="[labelCompact && 'hidden sm:block']">{{ label }}</span>
|
||||
<span v-if="labelCompact" class="sm:hidden">{{ labelCompact }}</span>
|
||||
</span>
|
||||
</slot>
|
||||
<Icon v-if="isTrailing && trailingIconName" :name="trailingIconName" :class="trailingIconClass" aria-hidden="true" />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, useSlots } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import type { RouteLocationNormalized } from 'vue-router'
|
||||
import type { RouteLocationNormalized, RouteLocationRaw } from 'vue-router'
|
||||
import NuxtLink from '#app/components/nuxt-link'
|
||||
import Icon from '../elements/Icon.vue'
|
||||
import { classNames } from '../../utils'
|
||||
@@ -34,6 +39,10 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
labelCompact: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@@ -81,7 +90,7 @@ const props = defineProps({
|
||||
default: false
|
||||
},
|
||||
to: {
|
||||
type: [String, Object] as PropType<string | RouteLocationNormalized>,
|
||||
type: [String, Object] as PropType<string | RouteLocationNormalized | RouteLocationRaw>,
|
||||
default: null
|
||||
},
|
||||
target: {
|
||||
@@ -127,6 +136,10 @@ const props = defineProps({
|
||||
truncate: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
compact: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
@@ -164,7 +177,7 @@ const buttonClass = computed(() => {
|
||||
return classNames(
|
||||
props.baseClass,
|
||||
$ui.button.size[props.size],
|
||||
$ui.button[isSquare.value ? 'square' : 'spacing'][props.size],
|
||||
$ui.button[isSquare.value ? 'square' : (props.compact ? 'compact' : 'spacing')][props.size],
|
||||
$ui.button.variant[props.variant],
|
||||
props.block ? 'w-full flex justify-center items-center' : 'inline-flex items-center',
|
||||
props.rounded ? 'rounded-full' : props.roundedClass,
|
||||
@@ -192,7 +205,7 @@ const leadingIconClass = computed(() => {
|
||||
return classNames(
|
||||
props.iconBaseClass,
|
||||
$ui.button.icon.size[props.size],
|
||||
(!!slots.default || !!props.label?.length) && $ui.button.icon.leading.spacing[props.size],
|
||||
(!!slots.default || !!props.label?.length) && $ui.button.icon.leading[props.compact ? 'compactSpacing' : 'spacing'][props.size],
|
||||
props.leadingIconClass,
|
||||
props.loading && 'animate-spin'
|
||||
)
|
||||
@@ -202,7 +215,7 @@ const trailingIconClass = computed(() => {
|
||||
return classNames(
|
||||
props.iconBaseClass,
|
||||
$ui.button.icon.size[props.size],
|
||||
(!!slots.default || !!props.label?.length) && $ui.button.icon.trailing.spacing[props.size],
|
||||
(!!slots.default || !!props.label?.length) && $ui.button.icon.trailing[props.compact ? 'compactSpacing' : 'spacing'][props.size],
|
||||
props.trailingIconClass,
|
||||
props.loading && !isLeading.value && 'animate-spin'
|
||||
)
|
||||
|
||||
@@ -1,17 +1,31 @@
|
||||
<template>
|
||||
<Menu v-slot="{ open }" as="div" :class="wrapperClass" @mouseleave="onMouseLeave">
|
||||
<MenuButton ref="trigger" as="div" class="inline-flex" @mouseover="onMouseOver">
|
||||
<MenuButton
|
||||
ref="trigger"
|
||||
as="div"
|
||||
:disabled="disabled"
|
||||
class="inline-flex w-full"
|
||||
role="button"
|
||||
@mouseover="onMouseOver"
|
||||
>
|
||||
<slot :open="open">
|
||||
<button>Open</button>
|
||||
<button :disabled="disabled">
|
||||
Open
|
||||
</button>
|
||||
</slot>
|
||||
</MenuButton>
|
||||
|
||||
<div v-if="open" ref="container" :class="[containerClass, widthClass]" @mouseover="onMouseOver">
|
||||
<div v-if="open && items.length" ref="container" :class="[containerClass, widthClass]" @mouseover="onMouseOver">
|
||||
<transition appear v-bind="transitionClass">
|
||||
<MenuItems :class="baseClass" static>
|
||||
<div v-for="(subItems, index) of items" :key="index" :class="groupClass">
|
||||
<MenuItem v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ active, disabled }" :disabled="item.disabled" as="div">
|
||||
<Component v-bind="item" :is="(item.to && NuxtLink) || (item.click && 'button') || 'div'" :class="resolveItemClass({ active, disabled })" @click="e => onItemClick(e, item)">
|
||||
<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 })"
|
||||
@click="item.click"
|
||||
>
|
||||
<slot :name="item.slot" :item="item">
|
||||
<Icon v-if="item.icon" :name="item.icon" :class="[itemIconClass, item.iconClass]" />
|
||||
<Avatar v-if="item.avatar" v-bind="{ size: 'xxs', ...item.avatar }" :class="itemAvatarClass" />
|
||||
@@ -38,14 +52,14 @@ import {
|
||||
MenuItems,
|
||||
MenuItem
|
||||
} from '@headlessui/vue'
|
||||
import type { Ref, PropType } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import type { RouteLocationNormalized } from 'vue-router'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import NuxtLink from '#app/components/nuxt-link'
|
||||
import Icon from '../elements/Icon.vue'
|
||||
import Avatar from '../elements/Avatar.vue'
|
||||
import { classNames } from '../../utils'
|
||||
import { classNames, omit } from '../../utils'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import type { Avatar as AvatarType } from '../../types/avatar'
|
||||
import type { PopperOptions } from '../../types'
|
||||
@@ -54,8 +68,8 @@ import $ui from '#build/ui'
|
||||
const props = defineProps({
|
||||
items: {
|
||||
type: Array as PropType<{
|
||||
to: RouteLocationNormalized
|
||||
exact: boolean
|
||||
to?: RouteLocationNormalized
|
||||
exact?: boolean
|
||||
label: string
|
||||
disabled?: boolean
|
||||
slot?: string
|
||||
@@ -74,6 +88,10 @@ const props = defineProps({
|
||||
return ['click', 'hover'].includes(value)
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
wrapperClass: {
|
||||
type: String,
|
||||
default: () => $ui.dropdown.wrapper
|
||||
@@ -128,7 +146,15 @@ const props = defineProps({
|
||||
},
|
||||
popperOptions: {
|
||||
type: Object as PropType<PopperOptions>,
|
||||
default: () => {}
|
||||
default: () => ({})
|
||||
},
|
||||
openDelay: {
|
||||
type: Number,
|
||||
default: 50
|
||||
},
|
||||
closeDelay: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
@@ -144,34 +170,21 @@ function resolveItemClass ({ active, disabled }: { active: boolean, disabled: bo
|
||||
)
|
||||
}
|
||||
|
||||
function onItemClick (e, item: any) {
|
||||
if (item.disabled) {
|
||||
return
|
||||
}
|
||||
// https://github.com/tailwindlabs/headlessui/blob/f66f4926c489fc15289d528294c23a3dc2aee7b1/packages/%40headlessui-vue/src/components/menu/menu.ts#L131
|
||||
const menuApi = ref<any>(null)
|
||||
|
||||
if (item.click) {
|
||||
item.click(e)
|
||||
}
|
||||
}
|
||||
|
||||
const menuApi: Ref<any> = ref(null)
|
||||
let openTimeout: NodeJS.Timeout | null = null
|
||||
let closeTimeout: NodeJS.Timeout | null = null
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
// @ts-expect-error internals
|
||||
const menuProvides = trigger.value?.$.provides
|
||||
if (!menuProvides) {
|
||||
return
|
||||
}
|
||||
const menuProvidesSymbols = Object.getOwnPropertySymbols(menuProvides)
|
||||
menuApi.value = menuProvidesSymbols.length && menuProvides[menuProvidesSymbols[0]]
|
||||
// stop trigger click propagation on hover
|
||||
menuApi.value?.buttonRef.addEventListener('click', (e: Event) => {
|
||||
if (props.mode === 'hover') {
|
||||
e.stopPropagation()
|
||||
}
|
||||
}, true)
|
||||
}, 200)
|
||||
})
|
||||
|
||||
@@ -192,7 +205,7 @@ function onMouseOver () {
|
||||
openTimeout = openTimeout || setTimeout(() => {
|
||||
menuApi.value.openMenu && menuApi.value.openMenu()
|
||||
openTimeout = null
|
||||
}, 50)
|
||||
}, props.openDelay)
|
||||
}
|
||||
|
||||
function onMouseLeave () {
|
||||
@@ -212,7 +225,7 @@ function onMouseLeave () {
|
||||
closeTimeout = closeTimeout || setTimeout(() => {
|
||||
menuApi.value.closeMenu && menuApi.value.closeMenu()
|
||||
closeTimeout = null
|
||||
}, 0)
|
||||
}, props.closeDelay)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -18,10 +18,15 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
const nuxtApp = useNuxtApp()
|
||||
const state = useState('u-icons', () => ({}))
|
||||
const state = useState<Record<string, Required<IconifyIcon> | Promise<void> | null>>('u-icons', () => ({}))
|
||||
|
||||
const isFetching = computed(() => !!state.value?.[props.name]?.then)
|
||||
const icon = computed<IconifyIcon | null>(() => !state.value?.[props.name] || state.value[props.name].then ? null : state.value[props.name])
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
|
||||
const isFetching = computed(() => state.value?.[props.name] && ('then' in state.value?.[props.name]!))
|
||||
const icon = computed<Required<IconifyIcon> | null>(() =>
|
||||
!state.value?.[props.name] || 'then' in state.value[props.name]!
|
||||
? null
|
||||
: state.value[props.name] as Required<IconifyIcon>
|
||||
)
|
||||
const component = computed(() => nuxtApp.vueApp.component(props.name))
|
||||
|
||||
const loadIconComponent = (name: string) => {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<router-link
|
||||
v-else
|
||||
v-slot="{ href, navigate, isActive, isExactActive }"
|
||||
v-bind="$props"
|
||||
v-bind="$props as RouterLinkProps"
|
||||
custom
|
||||
>
|
||||
<a
|
||||
@@ -25,10 +25,11 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import type { RouteLocationNormalized } from 'vue-router'
|
||||
import type { RouteLocationNormalized, RouterLinkProps } from 'vue-router'
|
||||
import { RouterLink } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
// @ts-expect-error internal props
|
||||
...RouterLink.props,
|
||||
to: {
|
||||
type: [String, Object] as PropType<string | RouteLocationNormalized>,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="rounded-md p-4" :class="variantClass">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<Icon :name="iconName" :class="iconClass" class="h-5 w-5" />
|
||||
<Icon v-if="iconName" :name="iconName" :class="iconClass" class="h-5 w-5" />
|
||||
</div>
|
||||
<div class="ml-3 flex-1 md:flex md:justify-between">
|
||||
<p v-if="title" class="text-sm leading-5" :class="titleClass">
|
||||
|
||||
@@ -37,7 +37,7 @@ const props = defineProps({
|
||||
default: null
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Number, Boolean, Array],
|
||||
type: [Boolean, Array],
|
||||
default: null
|
||||
},
|
||||
name: {
|
||||
|
||||
@@ -13,22 +13,21 @@
|
||||
:autocomplete="autocomplete"
|
||||
:spellcheck="spellcheck"
|
||||
:class="inputClass"
|
||||
@input="onInput($event.target.value)"
|
||||
@input="onInput(($event.target as any).value)"
|
||||
@focus="$emit('focus', $event)"
|
||||
@blur="$emit('blur', $event)"
|
||||
>
|
||||
<slot />
|
||||
<div v-if="isLeading" :class="iconLeadingWrapperClass">
|
||||
<Icon :name="iconName" :class="iconClass" />
|
||||
<Icon v-if="iconName" :name="iconName" :class="iconClass" />
|
||||
</div>
|
||||
<div v-if="isTrailing" :class="iconTrailingWrapperClass">
|
||||
<Icon :name="iconName" :class="iconClass" />
|
||||
<Icon v-if="iconName" :name="iconName" :class="iconClass" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Ref } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import Icon from '../elements/Icon.vue'
|
||||
import { classNames } from '../../utils'
|
||||
@@ -129,11 +128,11 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'focus', 'blur'])
|
||||
|
||||
const input: Ref<HTMLInputElement> = ref(null)
|
||||
const input = ref<HTMLInputElement | null>(null)
|
||||
|
||||
const autoFocus = () => {
|
||||
if (props.autofocus) {
|
||||
input.value.focus()
|
||||
input.value?.focus()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
:required="required"
|
||||
:disabled="disabled"
|
||||
:class="selectClass"
|
||||
@input="onInput($event.target.value)"
|
||||
@input="onInput(($event.target as any).value)"
|
||||
>
|
||||
<template v-for="(option, index) in normalizedOptionsWithPlaceholder">
|
||||
<optgroup
|
||||
|
||||
@@ -10,18 +10,18 @@
|
||||
:class="wrapperClass"
|
||||
@update:model-value="onUpdate"
|
||||
>
|
||||
<input :value="modelValue" :required="required" class="absolute inset-0 w-px opacity-0 cursor-default" tabindex="-1">
|
||||
<input :value="modelValue" :required="required" class="absolute inset-0 w-px opacity-0 cursor-default" tabindex="-1" aria-hidden="true">
|
||||
|
||||
<ComboboxButton ref="trigger" v-slot="{ disabled }" as="div">
|
||||
<slot :open="open" :disabled="disabled">
|
||||
<ComboboxButton ref="trigger" v-slot="{ disabled: buttonDisabled }" as="div" role="button" class="inline-flex w-full">
|
||||
<slot :open="open" :disabled="buttonDisabled">
|
||||
<button :class="selectCustomClass" :disabled="disabled" type="button">
|
||||
<slot name="label">
|
||||
<span v-if="modelValue" class="block truncate">{{ modelValue[textAttribute] }}</span>
|
||||
<span v-if="modelValue" class="block truncate">{{ (modelValue as any)[textAttribute] }}</span>
|
||||
<span v-else class="block truncate u-text-gray-400">{{ placeholder }}</span>
|
||||
</slot>
|
||||
<slot name="icon">
|
||||
<span :class="iconWrapperClass">
|
||||
<Icon :name="icon" :class="iconClass" aria-hidden="true" />
|
||||
<Icon v-if="icon" :name="icon" :class="iconClass" aria-hidden="true" />
|
||||
</span>
|
||||
</slot>
|
||||
</button>
|
||||
@@ -44,13 +44,13 @@
|
||||
/>
|
||||
<ComboboxOption
|
||||
v-for="(option, index) in filteredOptions"
|
||||
v-slot="{ active, selected, disabled }"
|
||||
v-slot="{ active, selected, disabled: optionDisabled }"
|
||||
:key="index"
|
||||
as="template"
|
||||
:value="option"
|
||||
:disabled="option.disabled"
|
||||
>
|
||||
<li :class="resolveOptionClass({ active, selected, disabled })">
|
||||
<li :class="resolveOptionClass({ active, selected, disabled: optionDisabled })">
|
||||
<div :class="listOptionContainerClass">
|
||||
<slot name="option" :option="option" :active="active" :selected="selected">
|
||||
<span class="block truncate">{{ option[textAttribute] }}</span>
|
||||
@@ -58,7 +58,7 @@
|
||||
</div>
|
||||
|
||||
<span v-if="selected" :class="resolveOptionIconClass({ active })">
|
||||
<Icon :name="listOptionIcon" :class="listOptionIconSizeClass" aria-hidden="true" />
|
||||
<Icon v-if="listOptionIcon" :name="listOptionIcon" :class="listOptionIconSizeClass" aria-hidden="true" />
|
||||
</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
@@ -84,7 +84,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import type { PropType, ComponentPublicInstance } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import {
|
||||
@@ -110,7 +110,7 @@ const props = defineProps({
|
||||
default: undefined
|
||||
},
|
||||
options: {
|
||||
type: Array as PropType<{ disabled?: boolean }[]>,
|
||||
type: Array as PropType<{ [key: string]: any, disabled?: boolean }[]>,
|
||||
default: () => []
|
||||
},
|
||||
required: {
|
||||
@@ -257,11 +257,11 @@ const props = defineProps({
|
||||
},
|
||||
popperOptions: {
|
||||
type: Object as PropType<PopperOptions>,
|
||||
default: () => {}
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const emit = defineEmits(['update:modelValue', 'open', 'close'])
|
||||
|
||||
const popperOptions = computed<PopperOptions>(() => defu({}, props.popperOptions, $ui.selectCustom.popperOptions))
|
||||
|
||||
@@ -307,6 +307,14 @@ const iconWrapperClass = classNames(
|
||||
$ui.selectCustom.icon.trailing.wrapper
|
||||
)
|
||||
|
||||
watch(container, (value) => {
|
||||
if (value) {
|
||||
emit('open')
|
||||
} else {
|
||||
emit('close')
|
||||
}
|
||||
})
|
||||
|
||||
function resolveOptionClass ({ active, selected, disabled }: { active: boolean, selected: boolean, disabled?: boolean }) {
|
||||
return classNames(
|
||||
props.listOptionBaseClass,
|
||||
@@ -323,7 +331,7 @@ function resolveOptionIconClass ({ active }: { active: boolean }) {
|
||||
)
|
||||
}
|
||||
|
||||
function onUpdate (event) {
|
||||
function onUpdate (event: any) {
|
||||
if (query.value && searchInput.value?.$el) {
|
||||
query.value = ''
|
||||
// explicitly set input text because `ComboboxInput` `displayValue` is not reactive
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
:placeholder="placeholder"
|
||||
:autocomplete="autocomplete"
|
||||
:class="textareaClass"
|
||||
@input="onInput($event.target.value)"
|
||||
@input="onInput(($event.target as any).value)"
|
||||
@focus="$emit('focus', $event)"
|
||||
@blur="$emit('blur', $event)"
|
||||
/>
|
||||
@@ -19,7 +19,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Ref } from 'vue'
|
||||
import { ref, computed, watch, onMounted, nextTick } from 'vue'
|
||||
import { classNames } from '../../utils'
|
||||
import $ui from '#build/ui'
|
||||
@@ -95,11 +94,11 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'focus', 'blur'])
|
||||
|
||||
const textarea: Ref<HTMLTextAreaElement> = ref(null)
|
||||
const textarea = ref<HTMLTextAreaElement | null>(null)
|
||||
|
||||
const autoFocus = () => {
|
||||
if (props.autofocus) {
|
||||
textarea.value.focus()
|
||||
textarea.value?.focus()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,19 +7,27 @@
|
||||
:nullable="nullable"
|
||||
@update:model-value="onSelect"
|
||||
>
|
||||
<div class="flex flex-col flex-1 min-h-0 divide-y divide-gray-100 dark:divide-gray-800">
|
||||
<div :class="$ui.commandPalette.wrapper">
|
||||
<div v-show="searchable" class="relative flex items-center">
|
||||
<Icon :name="inputIcon" class="pointer-events-none absolute top-3.5 left-5 h-5 w-5 u-text-gray-400" aria-hidden="true" />
|
||||
<Icon v-if="inputIcon" :name="inputIcon" :class="$ui.commandPalette.input.icon.base" aria-hidden="true" />
|
||||
<ComboboxInput
|
||||
ref="comboboxInput"
|
||||
:value="query"
|
||||
class="w-full h-12 pr-4 placeholder-gray-400 dark:placeholder-gray-500 bg-transparent border-0 pl-[3.25rem] u-text-gray-900 focus:ring-0 sm:text-sm"
|
||||
:class="$ui.commandPalette.input.base"
|
||||
:placeholder="inputPlaceholder"
|
||||
autocomplete="off"
|
||||
@change="query = $event.target.value"
|
||||
/>
|
||||
|
||||
<Button v-if="closeIcon" :icon="closeIcon" variant="transparent" class="absolute right-3" @click="onClear" />
|
||||
<Button
|
||||
v-if="inputCloseIcon"
|
||||
:icon="inputCloseIcon"
|
||||
:class="$ui.commandPalette.input.close.base"
|
||||
:size="$ui.commandPalette.input.close.size"
|
||||
:variant="$ui.commandPalette.input.close.variant"
|
||||
aria-label="close"
|
||||
@click="onClear"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ComboboxOptions v-if="groups.length" static hold class="relative flex-1 overflow-y-auto divide-y divide-gray-100 dark:divide-gray-800 scroll-py-2">
|
||||
@@ -31,8 +39,8 @@
|
||||
</ComboboxOptions>
|
||||
|
||||
<div v-else-if="placeholder" class="flex flex-col items-center justify-center flex-1 px-6 py-14 sm:px-14">
|
||||
<Icon :name="emptyIcon" class="w-6 h-6 mx-auto u-text-gray-400" aria-hidden="true" />
|
||||
<p class="mt-4 text-sm text-center u-text-gray-900">
|
||||
<Icon v-if="emptyIcon" :name="emptyIcon" class="w-6 h-6 mx-auto u-text-gray-400 mb-4" aria-hidden="true" />
|
||||
<p class="text-sm text-center u-text-gray-900">
|
||||
{{ query ? "We couldn't find any items with that term. Please try again." : "We couldn't find any items." }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -41,7 +49,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { Combobox, ComboboxInput, ComboboxOptions } from '@headlessui/vue'
|
||||
import type { ComputedRef, PropType, ComponentPublicInstance } from 'vue'
|
||||
import { useFuse } from '@vueuse/integrations/useFuse'
|
||||
@@ -52,6 +60,7 @@ import type { Group, Command } from '../../types/command-palette'
|
||||
import Icon from '../elements/Icon.vue'
|
||||
import Button from '../elements/Button.vue'
|
||||
import CommandPaletteGroup from './CommandPaletteGroup.vue'
|
||||
import $ui from '#build/ui'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
@@ -78,13 +87,13 @@ const props = defineProps({
|
||||
type: Array as PropType<Group[]>,
|
||||
default: () => []
|
||||
},
|
||||
closeIcon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
inputIcon: {
|
||||
type: String,
|
||||
default: 'heroicons-outline:search'
|
||||
default: () => $ui.commandPalette.input.icon.name
|
||||
},
|
||||
inputCloseIcon: {
|
||||
type: String,
|
||||
default: () => $ui.commandPalette.input.close.icon.name
|
||||
},
|
||||
inputPlaceholder: {
|
||||
type: String,
|
||||
@@ -92,7 +101,7 @@ const props = defineProps({
|
||||
},
|
||||
emptyIcon: {
|
||||
type: String,
|
||||
default: 'heroicons-outline:search'
|
||||
default: () => $ui.commandPalette.empty.icon.name
|
||||
},
|
||||
groupAttribute: {
|
||||
type: String,
|
||||
@@ -104,12 +113,16 @@ const props = defineProps({
|
||||
},
|
||||
options: {
|
||||
type: Object as PropType<Partial<UseFuseOptions<Command>>>,
|
||||
default: () => {}
|
||||
default: () => ({})
|
||||
},
|
||||
autoselect: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
autoclear: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
placeholder: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
@@ -130,6 +143,7 @@ onMounted(() => {
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
// @ts-expect-error internals
|
||||
const popoverProvides = comboboxInput.value?.$.provides
|
||||
if (!popoverProvides) {
|
||||
return
|
||||
@@ -166,14 +180,22 @@ const groups = computed(() => map(groupBy(results.value, command => command.item
|
||||
return {
|
||||
...props.groups.find(group => group.key === key),
|
||||
commands: commands.slice(0, options.value.resultLimit)
|
||||
}
|
||||
} as Group
|
||||
}))
|
||||
|
||||
watch(groups, () => {
|
||||
// Select first item on search changes
|
||||
setTimeout(() => {
|
||||
// https://github.com/tailwindlabs/headlessui/blob/6fa6074cd5d3a96f78a2d965392aa44101f5eede/packages/%40headlessui-vue/src/components/combobox/combobox.ts#L804
|
||||
comboboxInput.value?.$el.dispatchEvent(new KeyboardEvent('keydown', { key: 'PageUp' }))
|
||||
}, 0)
|
||||
})
|
||||
|
||||
// Methods
|
||||
|
||||
function activateFirstOption () {
|
||||
// hack combobox by using keyboard event
|
||||
// https://github.com/tailwindlabs/headlessui/blob/main/packages/%40headlessui-vue/src/components/combobox/combobox.ts#L692
|
||||
// https://github.com/tailwindlabs/headlessui/blob/6fa6074cd5d3a96f78a2d965392aa44101f5eede/packages/%40headlessui-vue/src/components/combobox/combobox.ts#L769
|
||||
setTimeout(() => {
|
||||
comboboxInput.value?.$el.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }))
|
||||
}, 0)
|
||||
@@ -183,7 +205,7 @@ function onSelect (option: Command | Command[]) {
|
||||
emit('update:modelValue', option, { query: query.value })
|
||||
|
||||
// Clear input after selection
|
||||
if (!props.multiple) {
|
||||
if (props.autoclear) {
|
||||
setTimeout(() => {
|
||||
query.value = ''
|
||||
}, 0)
|
||||
@@ -199,7 +221,7 @@ function onClear () {
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
updateQuery: (q) => {
|
||||
updateQuery: (q: string) => {
|
||||
query.value = q
|
||||
},
|
||||
comboboxApi,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<li class="p-2">
|
||||
<li class="p-2" role="option">
|
||||
<h2 v-if="group[groupAttribute]" class="px-3 my-2 text-xs font-semibold u-text-gray-900">
|
||||
{{ group[groupAttribute] }}
|
||||
</h2>
|
||||
@@ -20,12 +20,14 @@
|
||||
v-else-if="command.avatar"
|
||||
v-bind="{ size: 'xxxs', ...command.avatar }"
|
||||
class="flex-shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span v-else-if="command.chip" class="flex-shrink-0 w-2 h-2 mx-1 rounded-full" :style="{ background: `#${command.chip}` }" />
|
||||
|
||||
<div class="flex items-center gap-1.5 min-w-0" :class="{ 'opacity-50': command.disabled }">
|
||||
<slot :name="`${group.key}-command`" :group="group" :command="command">
|
||||
<span v-if="command.prefix" class="u-text-gray-400">{{ command.prefix }}</span>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span v-if="command.matches?.length" class="truncate" :class="{ 'flex-none': command.suffix }" v-html="highlight(command.matches[0])" />
|
||||
<span v-else class="truncate" :class="{ 'flex-none': command.suffix }">{{ command[commandAttribute] }}</span>
|
||||
<span v-if="command.suffix" class="u-text-gray-400 truncate">{{ command.suffix }}</span>
|
||||
@@ -71,7 +73,7 @@ defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
function highlight ({ indices, value }, i = 1) {
|
||||
function highlight ({ indices, value }: { indices: number[][], value:string }, i = 1): string {
|
||||
const pair = indices[indices.length - i]
|
||||
if (!pair) {
|
||||
return value
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
v-slot="{ isActive }"
|
||||
:key="index"
|
||||
v-bind="link"
|
||||
:class="[baseClass, spacingClass]"
|
||||
:class="[baseClass, spacingClass].join(' ')"
|
||||
:active-class="activeClass"
|
||||
:inactive-class="inactiveClass"
|
||||
@click="link.click && link.click()"
|
||||
@@ -39,6 +39,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { PropType } from 'vue'
|
||||
import type { RouteLocationNormalized } from 'vue-router'
|
||||
import Icon from '../elements/Icon.vue'
|
||||
import Link from '../elements/Link.vue'
|
||||
import Avatar from '../elements/Avatar.vue'
|
||||
@@ -48,6 +49,8 @@ import $ui from '#build/ui'
|
||||
defineProps({
|
||||
links: {
|
||||
type: Array as PropType<{
|
||||
to?: RouteLocationNormalized
|
||||
exact?: boolean
|
||||
label: string
|
||||
icon?: string
|
||||
iconClass?: string
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType, computed, toRef } from 'vue'
|
||||
import type { PropType, Ref } from 'vue'
|
||||
import { computed, toRef } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import type { VirtualElement } from '@popperjs/core'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import type { PopperOptions } from '../../types'
|
||||
import $ui from '#build/ui'
|
||||
@@ -46,7 +48,7 @@ const props = defineProps({
|
||||
},
|
||||
popperOptions: {
|
||||
type: Object as PropType<PopperOptions>,
|
||||
default: () => {}
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -61,7 +63,7 @@ const isOpen = computed({
|
||||
}
|
||||
})
|
||||
|
||||
const virtualElement = toRef(props, 'virtualElement')
|
||||
const virtualElement = toRef(props, 'virtualElement') as Ref<VirtualElement>
|
||||
|
||||
const popperOptions = computed<PopperOptions>(() => defu({}, props.popperOptions, $ui.contextMenu.popperOptions))
|
||||
|
||||
|
||||
@@ -20,14 +20,14 @@
|
||||
</p>
|
||||
|
||||
<div v-if="description && actions.length" class="mt-3 flex items-center gap-6">
|
||||
<button v-for="(action, index) of actions" :key="index" type="button" class="text-sm font-medium focus:outline-none text-primary-500 hover:text-primary-600 dark:hover:text-primary-400" @click.stop="onAction(action)">
|
||||
<button v-for="(action, index) of actions" :key="index" type="button" class="text-sm font-medium focus:outline-none text-primary-500 dark:text-primary-400 hover:text-primary-400 dark:hover:text-primary-500" @click.stop="onAction(action)">
|
||||
{{ action.label }}
|
||||
</button>
|
||||
</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" type="button" class="text-sm font-medium focus:outline-none text-primary-500 hover:text-primary-600 dark:hover:text-primary-400" @click.stop="onAction(action)">
|
||||
<button v-for="(action, index) of actions" :key="index" type="button" class="text-sm font-medium focus:outline-none text-primary-500 dark:text-primary-400 hover:text-primary-400 dark:hover:text-primary-500" @click.stop="onAction(action)">
|
||||
{{ action.label }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -56,6 +56,7 @@ import type { PropType } from 'vue'
|
||||
import Icon from '../elements/Icon.vue'
|
||||
import { useTimer } from '../../composables/useTimer'
|
||||
import { classNames } from '../../utils'
|
||||
import type { ToastNotificationAction } from '../../types'
|
||||
import $ui from '#build/ui'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -172,7 +173,7 @@ function onClose () {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
function onAction (action) {
|
||||
function onAction (action: ToastNotificationAction) {
|
||||
if (timer) {
|
||||
timer.stop()
|
||||
}
|
||||
|
||||
@@ -17,11 +17,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ToastNotification } from '../../types'
|
||||
import Notification from './Notification.vue'
|
||||
import { useNuxtApp, useState } from '#imports'
|
||||
|
||||
const { $toast } = useNuxtApp()
|
||||
const notifications = useState('notifications', () => [])
|
||||
const notifications = useState<ToastNotification[]>('notifications', () => [])
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
<template>
|
||||
<Popover v-slot="{ open, close }" :class="wrapperClass" @mouseleave="onMouseLeave">
|
||||
<PopoverButton ref="trigger" as="div" class="inline-flex" @mouseover="onMouseOver">
|
||||
<PopoverButton
|
||||
ref="trigger"
|
||||
as="div"
|
||||
:disabled="disabled"
|
||||
class="inline-flex w-full"
|
||||
role="button"
|
||||
@mouseover="onMouseOver"
|
||||
>
|
||||
<slot :open="open" :close="close">
|
||||
<button>Open</button>
|
||||
<button :disabled="disabled">
|
||||
Open
|
||||
</button>
|
||||
</slot>
|
||||
</PopoverButton>
|
||||
|
||||
@@ -17,8 +26,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Ref, PropType } from 'vue'
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
@@ -33,6 +42,10 @@ const props = defineProps({
|
||||
return ['click', 'hover'].includes(value)
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
wrapperClass: {
|
||||
type: String,
|
||||
default: () => $ui.popover.wrapper
|
||||
@@ -43,7 +56,7 @@ const props = defineProps({
|
||||
},
|
||||
widthClass: {
|
||||
type: String,
|
||||
default: () => $ui.tooltip.width
|
||||
default: () => $ui.popover.width
|
||||
},
|
||||
baseClass: {
|
||||
type: String,
|
||||
@@ -55,7 +68,15 @@ const props = defineProps({
|
||||
},
|
||||
popperOptions: {
|
||||
type: Object as PropType<PopperOptions>,
|
||||
default: () => {}
|
||||
default: () => ({})
|
||||
},
|
||||
openDelay: {
|
||||
type: Number,
|
||||
default: 50
|
||||
},
|
||||
closeDelay: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
@@ -63,25 +84,21 @@ const popperOptions = computed<PopperOptions>(() => defu({}, props.popperOptions
|
||||
|
||||
const [trigger, container] = usePopper(popperOptions.value)
|
||||
|
||||
const popoverApi: Ref<any> = ref(null)
|
||||
// https://github.com/tailwindlabs/headlessui/blob/f66f4926c489fc15289d528294c23a3dc2aee7b1/packages/%40headlessui-vue/src/components/popover/popover.ts#L151
|
||||
const popoverApi = ref<any>(null)
|
||||
|
||||
let openTimeout: NodeJS.Timeout | null = null
|
||||
let closeTimeout: NodeJS.Timeout | null = null
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
// @ts-expect-error internals
|
||||
const popoverProvides = trigger.value?.$.provides
|
||||
if (!popoverProvides) {
|
||||
return
|
||||
}
|
||||
const popoverProvidesSymbols = Object.getOwnPropertySymbols(popoverProvides)
|
||||
popoverApi.value = popoverProvidesSymbols.length && popoverProvides[popoverProvidesSymbols[0]]
|
||||
// stop trigger click propagation on hover
|
||||
popoverApi.value.button.addEventListener('click', (e: Event) => {
|
||||
if (props.mode === 'hover') {
|
||||
e.stopPropagation()
|
||||
}
|
||||
}, true)
|
||||
}, 200)
|
||||
})
|
||||
|
||||
@@ -89,6 +106,7 @@ function onMouseOver () {
|
||||
if (props.mode !== 'hover' || !popoverApi.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// cancel programmed closing
|
||||
if (closeTimeout) {
|
||||
clearTimeout(closeTimeout)
|
||||
@@ -101,13 +119,14 @@ function onMouseOver () {
|
||||
openTimeout = openTimeout || setTimeout(() => {
|
||||
popoverApi.value.togglePopover && popoverApi.value.togglePopover()
|
||||
openTimeout = null
|
||||
}, 50)
|
||||
}, props.openDelay)
|
||||
}
|
||||
|
||||
function onMouseLeave () {
|
||||
if (props.mode !== 'hover' || !popoverApi.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// cancel programmed opening
|
||||
if (openTimeout) {
|
||||
clearTimeout(openTimeout)
|
||||
@@ -120,7 +139,7 @@ function onMouseLeave () {
|
||||
closeTimeout = closeTimeout || setTimeout(() => {
|
||||
popoverApi.value.closePopover && popoverApi.value.closePopover()
|
||||
closeTimeout = null
|
||||
}, 0)
|
||||
}, props.closeDelay)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<TransitionRoot as="template" :appear="appear" :show="isOpen">
|
||||
<Dialog :class="[wrapperClass, { 'justify-end': side === 'right' }]" @close="isOpen = false">
|
||||
<Dialog :class="[wrapperClass, { 'justify-end': side === 'right' }]" @close="close">
|
||||
<TransitionChild
|
||||
v-if="overlay"
|
||||
as="template"
|
||||
@@ -89,7 +89,7 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const emit = defineEmits(['update:modelValue', 'close'])
|
||||
|
||||
const isOpen: WritableComputedRef<boolean> = computed({
|
||||
get () {
|
||||
@@ -129,6 +129,11 @@ const slideoverTransition = computed(() => {
|
||||
...props.transitionClass
|
||||
}
|
||||
})
|
||||
|
||||
function close (value: boolean) {
|
||||
isOpen.value = value
|
||||
emit('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
<template>
|
||||
<div ref="trigger" :class="wrapperClass" @mouseover="open = true" @mouseleave="open = false">
|
||||
<div ref="trigger" :class="wrapperClass" @mouseover="onMouseOver" @mouseleave="onMouseLeave">
|
||||
<slot :open="open">
|
||||
Hover me
|
||||
</slot>
|
||||
|
||||
<div v-if="open" ref="container" :class="[containerClass, widthClass]">
|
||||
<div v-if="open && !prevent" ref="container" :class="[containerClass, widthClass]">
|
||||
<transition appear v-bind="transitionClass">
|
||||
<div :class="baseClass">
|
||||
<div :class="[baseClass, backgroundClass, roundedClass, shadowClass, ringClass]">
|
||||
<slot name="text">
|
||||
{{ text }}
|
||||
<span class="truncate">{{ text }}</span>
|
||||
</slot>
|
||||
|
||||
<span v-if="shortcuts?.length" class="inline-flex items-center justify-end flex-shrink-0 gap-0.5 ml-1">
|
||||
<span class="mr-1 u-text-gray-700">·</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] u-bg-gray-100 rounded u-text-gray-900">
|
||||
{{ shortcut }}
|
||||
</kbd>
|
||||
</span>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
@@ -29,6 +36,14 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
prevent: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
shortcuts: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => []
|
||||
},
|
||||
wrapperClass: {
|
||||
type: String,
|
||||
default: () => $ui.tooltip.wrapper
|
||||
@@ -41,6 +56,22 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: () => $ui.tooltip.width
|
||||
},
|
||||
backgroundClass: {
|
||||
type: String,
|
||||
default: () => $ui.tooltip.background
|
||||
},
|
||||
shadowClass: {
|
||||
type: String,
|
||||
default: () => $ui.tooltip.shadow
|
||||
},
|
||||
ringClass: {
|
||||
type: String,
|
||||
default: () => $ui.tooltip.ring
|
||||
},
|
||||
roundedClass: {
|
||||
type: String,
|
||||
default: () => $ui.tooltip.rounded
|
||||
},
|
||||
baseClass: {
|
||||
type: String,
|
||||
default: () => $ui.tooltip.base
|
||||
@@ -51,7 +82,15 @@ const props = defineProps({
|
||||
},
|
||||
popperOptions: {
|
||||
type: Object as PropType<PopperOptions>,
|
||||
default: () => {}
|
||||
default: () => ({})
|
||||
},
|
||||
openDelay: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
closeDelay: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
@@ -60,6 +99,43 @@ const popperOptions = computed<PopperOptions>(() => defu({}, props.popperOptions
|
||||
const [trigger, container] = usePopper(popperOptions.value)
|
||||
|
||||
const open = ref(false)
|
||||
|
||||
let openTimeout: NodeJS.Timeout | null = null
|
||||
let closeTimeout: NodeJS.Timeout | null = null
|
||||
|
||||
// Methods
|
||||
|
||||
function onMouseOver () {
|
||||
// cancel programmed closing
|
||||
if (closeTimeout) {
|
||||
clearTimeout(closeTimeout)
|
||||
closeTimeout = null
|
||||
}
|
||||
// dropdown already open
|
||||
if (open.value) {
|
||||
return
|
||||
}
|
||||
openTimeout = openTimeout || setTimeout(() => {
|
||||
open.value = true
|
||||
openTimeout = null
|
||||
}, props.openDelay)
|
||||
}
|
||||
|
||||
function onMouseLeave () {
|
||||
// cancel programmed opening
|
||||
if (openTimeout) {
|
||||
clearTimeout(openTimeout)
|
||||
openTimeout = null
|
||||
}
|
||||
// dropdown already closed
|
||||
if (!open.value) {
|
||||
return
|
||||
}
|
||||
closeTimeout = closeTimeout || setTimeout(() => {
|
||||
open.value = false
|
||||
closeTimeout = null
|
||||
}, props.closeDelay)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ref, onMounted, watchEffect } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
import { popperGenerator, defaultModifiers } from '@popperjs/core/lib/popper-lite'
|
||||
import { popperGenerator, defaultModifiers, VirtualElement } from '@popperjs/core/lib/popper-lite'
|
||||
import type { Instance } from '@popperjs/core'
|
||||
import { omitBy, isUndefined } from 'lodash-es'
|
||||
import flip from '@popperjs/core/lib/modifiers/flip'
|
||||
@@ -8,6 +8,7 @@ import offset from '@popperjs/core/lib/modifiers/offset'
|
||||
import preventOverflow from '@popperjs/core/lib/modifiers/preventOverflow'
|
||||
import computeStyles from '@popperjs/core/lib/modifiers/computeStyles'
|
||||
import eventListeners from '@popperjs/core/lib/modifiers/eventListeners'
|
||||
import { MaybeElement, unrefElement } from '@vueuse/core'
|
||||
import type { PopperOptions } from '../types'
|
||||
|
||||
export const createPopper = popperGenerator({
|
||||
@@ -25,21 +26,22 @@ export function usePopper ({
|
||||
resize = true,
|
||||
placement,
|
||||
strategy
|
||||
}: PopperOptions, virtualReference: Ref<Object> = null) {
|
||||
const reference: Ref<HTMLElement> = ref(null)
|
||||
const popper: Ref<HTMLElement> = ref(null)
|
||||
const instance: Ref<Instance> = ref(null)
|
||||
}: PopperOptions, virtualReference?: Ref<Element | VirtualElement>) {
|
||||
const reference = ref<MaybeElement>(null)
|
||||
const popper = ref<MaybeElement>(null)
|
||||
const instance = ref<Instance | null>(null)
|
||||
|
||||
onMounted(() => {
|
||||
watchEffect((onInvalidate) => {
|
||||
if (!popper.value) { return }
|
||||
if (!reference.value && !virtualReference.value) { return }
|
||||
if (!reference.value && !virtualReference?.value) { return }
|
||||
|
||||
const popperEl = popper.value.$el || popper.value
|
||||
const referenceEl = virtualReference?.value || reference.value.$el || reference.value
|
||||
const popperEl = unrefElement(popper)
|
||||
const referenceEl = virtualReference?.value || unrefElement(reference)
|
||||
|
||||
// if (!(referenceEl instanceof HTMLElement)) { return }
|
||||
if (!(popperEl instanceof HTMLElement)) { return }
|
||||
if (!referenceEl) { return }
|
||||
|
||||
instance.value = createPopper(referenceEl, popperEl, omitBy({
|
||||
placement,
|
||||
@@ -76,5 +78,5 @@ export function usePopper ({
|
||||
})
|
||||
})
|
||||
|
||||
return [reference, popper, instance]
|
||||
return [reference, popper, instance] as const
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Ref, ref, computed } from 'vue-demi'
|
||||
import { ref, computed } from 'vue-demi'
|
||||
import { useTimestamp } from '@vueuse/core'
|
||||
|
||||
export function useTimer (cb: (...args: unknown[]) => any, interval: number) {
|
||||
let timer: number | null = null
|
||||
const timestamp = useTimestamp({ controls: true })
|
||||
const startTime: Ref<number | null> = ref(null)
|
||||
const startTime = ref<number | null>(null)
|
||||
|
||||
const remaining = computed(() => {
|
||||
if (!startTime.value) {
|
||||
@@ -46,7 +46,7 @@ export function useTimer (cb: (...args: unknown[]) => any, interval: number) {
|
||||
}
|
||||
|
||||
function resume () {
|
||||
startTime.value += (Date.now() - timestamp.timestamp.value)
|
||||
startTime.value = (startTime.value || 0) + (Date.now() - timestamp.timestamp.value)
|
||||
timestamp.resume()
|
||||
set()
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { Ref } from 'vue'
|
||||
import { defineNuxtPlugin, useState } from '#app'
|
||||
import type { ToastNotification } from '../types'
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
const notifications: Ref<ToastNotification[]> = useState('notifications', () => [])
|
||||
const notifications = useState<ToastNotification[]>('notifications', () => [])
|
||||
|
||||
function addNotification (notification: Partial<ToastNotification>) {
|
||||
const body = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export default (variantColors: string[]) => {
|
||||
export default function defaultPreset (variantColors: string[]) {
|
||||
const button = {
|
||||
base: 'font-medium focus:outline-none disabled:cursor-not-allowed disabled:opacity-75 focus:ring-offset-white dark:focus:ring-offset-black',
|
||||
rounded: 'rounded-md',
|
||||
@@ -26,6 +26,14 @@ export default (variantColors: string[]) => {
|
||||
lg: 'p-2',
|
||||
xl: 'p-3'
|
||||
},
|
||||
compact: {
|
||||
xxs: 'p-1 sm:px-2',
|
||||
xs: 'p-1.5 sm:px-2.5',
|
||||
sm: 'p-2 sm:px-3',
|
||||
md: 'p-2 sm:px-4',
|
||||
lg: 'p-2 sm:px-4',
|
||||
xl: 'p-3 sm:px-6'
|
||||
},
|
||||
variant: {
|
||||
...variantColors.reduce((acc: any, color: string) => {
|
||||
acc[color] = `shadow-sm border border-transparent text-white bg-${color}-600 hover:bg-${color}-700 disabled:bg-${color}-600 focus:ring-2 focus:ring-offset-2 focus:ring-${color}-500`
|
||||
@@ -57,6 +65,14 @@ export default (variantColors: string[]) => {
|
||||
md: '-ml-1 mr-2',
|
||||
lg: '-ml-1 mr-3',
|
||||
xl: '-ml-1 mr-3'
|
||||
},
|
||||
compactSpacing: {
|
||||
xxs: 'sm:-ml-0.5 sm:mr-1',
|
||||
xs: 'sm:-ml-0.5 sm:mr-1.5',
|
||||
sm: 'sm:-ml-0.5 sm:mr-2',
|
||||
md: 'sm:-ml-1 sm:mr-2',
|
||||
lg: 'sm:-ml-1 sm:mr-3',
|
||||
xl: 'sm:-ml-1 sm:mr-3'
|
||||
}
|
||||
},
|
||||
trailing: {
|
||||
@@ -67,6 +83,14 @@ export default (variantColors: string[]) => {
|
||||
md: 'ml-2 -mr-1',
|
||||
lg: 'ml-3 -mr-1',
|
||||
xl: 'ml-3 -mr-1'
|
||||
},
|
||||
compactSpacing: {
|
||||
xxs: 'sm:ml-1 sm:-mr-0.5',
|
||||
xs: 'sm:ml-1.5 sm:-mr-0.5',
|
||||
sm: 'sm:ml-2 sm:-mr-0.5',
|
||||
md: 'sm:ml-2 sm:-mr-1',
|
||||
lg: 'sm:ml-3 sm:-mr-1',
|
||||
xl: 'sm:ml-3 sm:-mr-1'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -342,7 +366,7 @@ export default (variantColors: string[]) => {
|
||||
wrapper: 'relative inline-flex text-left',
|
||||
container: 'z-20',
|
||||
width: 'w-48',
|
||||
base: 'u-bg-white divide-y u-divide-gray-100 rounded-md ring-1 u-ring-gray-200 shadow-lg',
|
||||
base: 'u-bg-white divide-y u-divide-gray-100 rounded-md ring-1 u-ring-gray-200 shadow-lg focus:outline-none',
|
||||
group: 'py-1',
|
||||
item: {
|
||||
base: 'group flex items-center gap-3 px-4 py-2 text-sm w-full',
|
||||
@@ -487,7 +511,11 @@ export default (variantColors: string[]) => {
|
||||
wrapper: 'relative inline-flex',
|
||||
container: 'z-20',
|
||||
width: 'max-w-xs',
|
||||
base: 'invisible w-auto h-6 px-2 py-1 space-x-1 truncate rounded shadow lg:visible u-bg-gray-800 truncate u-text-gray-50 text-xs',
|
||||
background: 'u-bg-white',
|
||||
shadow: 'shadow',
|
||||
rounded: 'rounded',
|
||||
ring: 'ring-1 u-ring-gray-200',
|
||||
base: 'invisible lg:visible h-6 px-2 py-1 text-xs font-normal',
|
||||
transition: {
|
||||
enterActiveClass: 'transition ease-out duration-200',
|
||||
enterFromClass: 'opacity-0 translate-y-1',
|
||||
@@ -538,6 +566,30 @@ export default (variantColors: string[]) => {
|
||||
}
|
||||
}
|
||||
|
||||
const commandPalette = {
|
||||
wrapper: 'flex flex-col flex-1 min-h-0 divide-y divide-gray-100 dark:divide-gray-800',
|
||||
input: {
|
||||
base: 'w-full h-12 pr-4 placeholder-gray-400 dark:placeholder-gray-500 bg-transparent border-0 pl-[3.25rem] u-text-gray-900 focus:ring-0 sm:text-sm',
|
||||
icon: {
|
||||
base: 'pointer-events-none absolute top-3.5 left-5 h-5 w-5 u-text-gray-400',
|
||||
name: 'heroicons-outline:search'
|
||||
},
|
||||
close: {
|
||||
base: 'absolute right-2',
|
||||
variant: 'transparent',
|
||||
size: 'md',
|
||||
icon: {
|
||||
name: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
empty: {
|
||||
icon: {
|
||||
name: 'heroicons-outline:search'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
card,
|
||||
modal,
|
||||
@@ -563,6 +615,9 @@ export default (variantColors: string[]) => {
|
||||
notification,
|
||||
tooltip,
|
||||
popover,
|
||||
contextMenu
|
||||
contextMenu,
|
||||
commandPalette
|
||||
}
|
||||
}
|
||||
|
||||
export type DefaultPreset = ReturnType<typeof defaultPreset>
|
||||
|
||||
7
src/runtime/types/command-palette.d.ts
vendored
7
src/runtime/types/command-palette.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
import type { UseFuseOptions } from '@vueuse/integrations/useFuse'
|
||||
import type { FuseSortFunctionMatch, FuseSortFunctionMatchList } from 'fuse.js'
|
||||
import type { Avatar } from './avatar'
|
||||
|
||||
export interface Command {
|
||||
id: string | number
|
||||
prefix?: string
|
||||
@@ -11,6 +11,10 @@ export interface Command {
|
||||
chip?: string
|
||||
disabled?: boolean
|
||||
shortcuts?: string[]
|
||||
group?: string
|
||||
score?: number
|
||||
matches?: (FuseSortFunctionMatch | FuseSortFunctionMatchList)[]
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface Group {
|
||||
@@ -19,4 +23,5 @@ export interface Group {
|
||||
inactive?: string
|
||||
commands: Command[]
|
||||
options?: Partial<UseFuseOptions<Command>>
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
11
src/runtime/types/toast.d.ts
vendored
11
src/runtime/types/toast.d.ts
vendored
@@ -1,3 +1,8 @@
|
||||
export interface ToastNotificationAction {
|
||||
label: string,
|
||||
click: Function
|
||||
}
|
||||
|
||||
export interface ToastNotification {
|
||||
id: string
|
||||
title: string
|
||||
@@ -5,10 +10,8 @@ export interface ToastNotification {
|
||||
type: string
|
||||
icon?: string
|
||||
timeout: number
|
||||
actions?: {
|
||||
label: string,
|
||||
click: Function
|
||||
}[]
|
||||
actions?: ToastNotificationAction[]
|
||||
click?: Function
|
||||
callback?: Function
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
{
|
||||
"extends": "./docs/.nuxt/tsconfig.json"
|
||||
"extends": "./docs/.nuxt/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "esnext"
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"./docs/.nuxt/nuxt.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user