diff --git a/.eslintrc.cjs b/.eslintrc.cjs index cf4eeacd..b2030aed 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -3,6 +3,7 @@ module.exports = { extends: ['@nuxt/eslint-config'], rules: { 'semi': ['error', 'never'], + 'quotes': ['error', 'single'], 'comma-dangle': ['error', 'never'], 'space-before-function-paren': ['error', 'always'], 'vue/multi-word-component-names': 0, diff --git a/docs/components/content/examples/AccordionExampleBasic.vue b/docs/components/content/examples/AccordionExampleBasic.vue new file mode 100644 index 00000000..1225ad9a --- /dev/null +++ b/docs/components/content/examples/AccordionExampleBasic.vue @@ -0,0 +1,33 @@ + + + diff --git a/docs/components/content/examples/AccordionExampleDefaultSlot.vue b/docs/components/content/examples/AccordionExampleDefaultSlot.vue new file mode 100644 index 00000000..f1d70ff1 --- /dev/null +++ b/docs/components/content/examples/AccordionExampleDefaultSlot.vue @@ -0,0 +1,52 @@ + + + diff --git a/docs/components/content/examples/AccordionExampleItemSlot.vue b/docs/components/content/examples/AccordionExampleItemSlot.vue new file mode 100644 index 00000000..978768cd --- /dev/null +++ b/docs/components/content/examples/AccordionExampleItemSlot.vue @@ -0,0 +1,74 @@ + + + diff --git a/docs/components/content/examples/CommandPaletteExampleAsync.vue b/docs/components/content/examples/CommandPaletteExampleAsync.vue index d4ca13f7..02f0c685 100644 --- a/docs/components/content/examples/CommandPaletteExampleAsync.vue +++ b/docs/components/content/examples/CommandPaletteExampleAsync.vue @@ -8,7 +8,7 @@ const groups = computed(() => { return [] } - const users = await $fetch(`https://jsonplaceholder.typicode.com/users`, { params: { q } }) + const users = await $fetch('https://jsonplaceholder.typicode.com/users', { params: { q } }) return users.map(user => ({ id: user.id, label: user.name, suffix: user.email })) } diff --git a/docs/content/2.elements/5.accordion.md b/docs/content/2.elements/5.accordion.md new file mode 100644 index 00000000..74b772c5 --- /dev/null +++ b/docs/content/2.elements/5.accordion.md @@ -0,0 +1,258 @@ +--- +github: true +description: Display togglable accordion panels. +headlessui: + label: 'Accordion' + to: 'https://headlessui.com/vue/disclosure' +navigation: + badge: 'Edge' +--- + +## Usage + +Pass an array to the `items` prop of the Accordion component. Each item can have any property from the [Button](/elements/button) component such as `label`, `icon`, `color`, `variant`, `size`, etc. but also: + +- `slot` - A key to customize the item with a slot. +- `content` - The content to display in the panel by default. +- `disabled` - Determines whether the item is disabled or not. +- `defaultOpen` - Determines whether the item is initially open or closed. + +::component-example +#default +:accordion-example-basic + +#code +```vue + + + +``` +:: + +### Style + +You can also pass any prop from the [Button](/elements/button) component directly to the Accordion component to style the buttons. + +::component-card +--- +baseProps: + items: + - label: "1. What is NuxtLabs UI?" + content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit" + - label: "2. Getting Started" + content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit" + - label: "3. Theming" + content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit" + - label: "4. Components" + content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit" +props: + color: 'primary' + variant: 'soft' + size: 'sm' +ui: + variant: + solid: 1 + outline: 1 + ghost: 1 + soft: 1 + link: 1 + size: + 2xs: '' + xs: '' + sm: '' + md: '' + lg: '' + xl: '' +--- +:: + +### Icon + +Use any icon from [Iconify](https://icones.js.org) by setting the `open-icon` and `close-icon` props by using this pattern: `i-{collection_name}-{icon_name}` or change it globally in `ui.accordion.default.openIcon` and `ui.accordion.default.closeIcon`. + +You can also set them to `null` to hide the icons. + +::component-card +--- +baseProps: + items: + - label: "1. What is NuxtLabs UI?" + content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit" + - label: "2. Getting Started" + content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit" + - label: "3. Theming" + content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit" + - label: "4. Components" + content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit" +props: + openIcon: 'i-heroicons-plus' + closeIcon: 'i-heroicons-minus' +excludedProps: + - openIcon + - closeIcon +--- +:: + +### Open + +Use the `default-open` prop to open all items by default. + +::component-card +--- +baseProps: + items: + - label: "What is NuxtLabs UI?" + content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit" + - label: "Getting Started" + content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit" + - label: "Theming" + content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit" + - label: "Components" + content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit" +props: + defaultOpen: true +excludedProps: + - defaultOpen +--- +:: + +## Slots + +You can use slots to customize the buttons and items content of the Accordion. + +### `default` + +Use the `#default` slot to customize the trigger buttons. You will have access to the `item`, `index`, `open` properties and `close` method in the slot scope. + +::component-example +#default +:accordion-example-default-slot + +#code +```vue + + + +``` +:: + +### `item` + +Use the `#item` slot to customize the items content or pass a `slot` property to customize a specific item. You will have access to the `item`, `index`, `open` properties and `close` method in the slot scope. + +::component-example +#default +:accordion-example-item-slot + +#code +```vue + + + +``` +:: + +## Props + +:component-props + +## Preset + +:component-preset diff --git a/docs/content/2.elements/5.icon.md b/docs/content/2.elements/6.icon.md similarity index 100% rename from docs/content/2.elements/5.icon.md rename to docs/content/2.elements/6.icon.md diff --git a/docs/content/2.elements/6.kbd.md b/docs/content/2.elements/7.kbd.md similarity index 100% rename from docs/content/2.elements/6.kbd.md rename to docs/content/2.elements/7.kbd.md diff --git a/docs/plugins/ui.ts b/docs/plugins/ui.ts index 945c632e..46011a8b 100644 --- a/docs/plugins/ui.ts +++ b/docs/plugins/ui.ts @@ -1,4 +1,4 @@ -import { hexToRgb } from "../../src/runtime/utils" +import { hexToRgb } from '../../src/runtime/utils' import colors from '#tailwind-config/theme/colors' export default defineNuxtPlugin({ diff --git a/src/module.ts b/src/module.ts index 6a45bd2b..02d8632d 100644 --- a/src/module.ts +++ b/src/module.ts @@ -137,7 +137,7 @@ export default defineNuxtModule({ config: { darkMode: 'class', plugins: [ - require("@tailwindcss/forms")({ strategy: 'class' }), + require('@tailwindcss/forms')({ strategy: 'class' }), require('@tailwindcss/aspect-ratio'), require('@tailwindcss/typography'), require('@tailwindcss/container-queries') diff --git a/src/runtime/app.config.ts b/src/runtime/app.config.ts index d71f353a..1c3715f0 100644 --- a/src/runtime/app.config.ts +++ b/src/runtime/app.config.ts @@ -248,7 +248,7 @@ const dropdown = { enterActiveClass: 'transition duration-100 ease-out', enterFromClass: 'transform scale-95 opacity-0', enterToClass: 'transform scale-100 opacity-100', - leaveActiveClass: 'transition duration-75 ease-out', + leaveActiveClass: 'transition duration-75 ease-in', leaveFromClass: 'transform scale-100 opacity-100', leaveToClass: 'transform scale-95 opacity-0' }, @@ -258,6 +258,29 @@ const dropdown = { } } +const accordion = { + wrapper: 'w-full flex flex-col gap-y-2', + item: { + base: '', + size: 'text-sm', + color: 'text-gray-500 dark:text-gray-400', + padding: 'py-2' + }, + transition: { + enterActiveClass: 'transition duration-100 ease-in', + enterFromClass: 'transform opacity-0', + enterToClass: 'transform opacity-100', + leaveActiveClass: 'transition duration-75 ease-out', + leaveFromClass: 'transform opacity-100', + leaveToClass: 'transform opacity-0' + }, + default: { + openIcon: 'i-heroicons-chevron-down-20-solid', + closeIcon: '', + variant: 'soft' + } +} + const kbd = { base: 'inline-flex items-center justify-center text-gray-900 dark:text-white', padding: 'px-1', @@ -387,7 +410,7 @@ const formGroup = { label: { wrapper: 'flex content-center justify-between', base: 'block text-sm font-medium text-gray-700 dark:text-gray-200', - required: `after:content-['*'] after:ms-0.5 after:text-red-500 dark:after:text-red-400` + required: 'after:content-[\'*\'] after:ms-0.5 after:text-red-500 dark:after:text-red-400' }, description: 'text-sm text-gray-500 dark:text-gray-400', container: 'mt-1 relative', @@ -541,7 +564,7 @@ const range = { background: 'bg-{color}-500 dark:bg-{color}-400' }, thumb: { - base: `[&::-webkit-slider-thumb]:relative [&::-moz-range-thumb]:relative [&::-webkit-slider-thumb]:z-[1] [&::-moz-range-thumb]:z-[1] [&::-webkit-slider-thumb]:appearance-none [&::-moz-range-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-0`, + base: '[&::-webkit-slider-thumb]:relative [&::-moz-range-thumb]:relative [&::-webkit-slider-thumb]:z-[1] [&::-moz-range-thumb]:z-[1] [&::-webkit-slider-thumb]:appearance-none [&::-moz-range-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-0', color: 'text-{color}-500 dark:text-{color}-400', background: '[&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:dark:bg-gray-900 [&::-moz-range-thumb]:bg-current', ring: '[&::-webkit-slider-thumb]:ring-2 [&::-webkit-slider-thumb]:ring-current', @@ -926,6 +949,7 @@ export default { buttonGroup, dropdown, kbd, + accordion, input, formGroup, textarea, diff --git a/src/runtime/components/elements/Accordion.vue b/src/runtime/components/elements/Accordion.vue new file mode 100644 index 00000000..1ce841ad --- /dev/null +++ b/src/runtime/components/elements/Accordion.vue @@ -0,0 +1,93 @@ + + +