Merge pull request #11 from Chank1e/chank1e/alert

feat: alert, all props made reactive for all components, tabs
This commit is contained in:
Alexandr P
2022-07-05 19:57:32 +03:00
committed by GitHub
50 changed files with 1111 additions and 115 deletions

View File

@@ -19,5 +19,6 @@ module.exports = {
'vue/multi-word-component-names': 'off', 'vue/multi-word-component-names': 'off',
'comma-dangle': ['error', 'always-multiline'], 'comma-dangle': ['error', 'always-multiline'],
'no-multiple-empty-lines': 'error', 'no-multiple-empty-lines': 'error',
'object-curly-spacing': ['error', 'always'],
}, },
} }

18
docs/.postcssrc.js Normal file
View File

@@ -0,0 +1,18 @@
// https://github.com/vuejs/vitepress/issues/199#issuecomment-1168325262
// by default vitepress adds .vp-doc h1|h2|h3...p and others tags inside vitepress document.
// here we add :not(:where(.vp-raw *)) selector to use it inside component examples
// to prevent component style pollution from .vp-doc styles
module.exports = {
plugins: {
'tailwindcss': {},
'postcss-prefix-selector': {
prefix: ':not(:where(.vp-raw *))',
includeFiles: [/vp-doc\.css/],
transform(prefix, _selector) {
const [selector, pseudo = ''] = _selector.split(/(:\S*)$/)
return selector + prefix + pseudo
}
},
}
}

View File

@@ -16,9 +16,11 @@ function buildSidebar() {
function getComponents() { function getComponents() {
return [ return [
{ text: 'Alert', link: '/guide/alert/alert.md' },
{ text: 'Button', link: '/guide/button/button.md' }, { text: 'Button', link: '/guide/button/button.md' },
{ text: 'Button Group', link: '/guide/buttonGroup/buttonGroup.md' }, { text: 'Button Group', link: '/guide/buttonGroup/buttonGroup.md' },
{ text: 'Spinner', link: '/guide/spinner/spinner.md' }, { text: 'Spinner', link: '/guide/spinner/spinner.md' },
{ text: 'Tabs', link: '/guide/tabs/tabs.md' },
] ]
} }
@@ -27,7 +29,7 @@ function getComponents() {
* https://github.com/vuejs/vitepress/blob/master/docs/.vitepress/config.js * https://github.com/vuejs/vitepress/blob/master/docs/.vitepress/config.js
*/ */
export default { export default {
title: 'flowbite-vue vitepress', title: 'Flowbite Vue 3 Components',
themeConfig: { themeConfig: {
docsDir: 'docs', docsDir: 'docs',
sidebar: buildSidebar(), sidebar: buildSidebar(),

208
docs/guide/alert/alert.md Normal file
View File

@@ -0,0 +1,208 @@
<script setup>
import AlertTypeExample from './examples/AlertTypeExample.vue';
import AlertTitleExample from './examples/AlertTitleExample.vue';
import AlertClosableExample from './examples/AlertClosableExample.vue';
import AlertBorderExample from './examples/AlertBorderExample.vue';
import AlertIconExample from './examples/AlertIconExample.vue';
import AlertInlineExample from './examples/AlertInlineExample.vue';
</script>
# Alert
reference: [https://flowbite.com/docs/components/alert/](https://flowbite.com/docs/components/alert/)
## Prop - type
```typescript
export type AlertType = 'info' | 'danger' | 'success' | 'warning' | 'dark'
defineProps({
type: {
type: String as PropType<AlertType>,
default: 'info',
},
})
```
<AlertTypeExample />
```vue
<script setup>
import { Alert } from 'flowbite-vue'
</script>
<template>
<div class="flex flex-col">
<Alert type="info" class="mb-2">Info</Alert>
<Alert type="warning" class="mb-2">Warning</Alert>
<Alert type="danger" class="mb-2">Danger</Alert>
<Alert type="dark" class="mb-2">Dark</Alert>
<Alert type="success">Success</Alert>
</div>
</template>
```
## Prop - title
```typescript
defineProps({
title: {
type: String,
default: '',
},
})
```
<AlertTitleExample />
```vue
<script setup>
import { Alert } from 'flowbite-vue'
</script>
<template>
<div class="flex flex-col">
<Alert type="info" title="Some info title" class="mb-2">Info</Alert>
<Alert type="warning" title="Some warning title" class="mb-2">Warning</Alert>
<Alert type="danger" title="Some danger title" class="mb-2">Danger</Alert>
<Alert type="dark" title="Some dark title" class="mb-2">Dark</Alert>
<Alert type="success" title="Some success title">Success</Alert>
</div>
</template>
```
## Prop - closable
```typescript
defineProps({
closable: {
type: Boolean,
default: false,
},
})
```
<AlertClosableExample />
```vue
<script setup>
import { Alert } from 'flowbite-vue'
</script>
<template>
<div class="flex flex-col">
<Alert type="info" closable class="mb-2">Info</Alert>
<Alert type="warning" closable class="mb-2">Warning</Alert>
<Alert type="danger" closable class="mb-2">Danger</Alert>
<Alert type="dark" closable class="mb-2">Dark</Alert>
<Alert type="success" closable>Success</Alert>
</div>
</template>
```
## Prop - border
```typescript
defineProps({
border: {
type: Boolean,
default: false,
},
})
```
<AlertBorderExample />
```vue
<script setup>
import { Alert } from 'flowbite-vue'
</script>
<template>
<div class="flex flex-col">
<Alert type="info" border class="mb-2">Info</Alert>
<Alert type="warning" border class="mb-2">Warning</Alert>
<Alert type="danger" border class="mb-2">Danger</Alert>
<Alert type="dark" border class="mb-2">Dark</Alert>
<Alert type="success" border>Success</Alert>
</div>
</template>
```
## Prop - icon
```typescript
defineProps({
icon: {
type: Boolean,
default: true,
},
})
```
<AlertIconExample />
```vue
<script setup>
import { Alert } from 'flowbite-vue'
</script>
<template>
<div class="flex flex-col">
<Alert type="info" :icon="false" class="mb-2">Info</Alert>
<Alert title="WARNING" type="warning" :icon="false" class="mb-2">Warning</Alert>
<Alert type="danger" :icon="false" class="mb-2">Danger</Alert>
<Alert type="dark" :icon="false" class="mb-2">Dark</Alert>
<Alert type="success" :icon="false">Success</Alert>
</div>
</template>
```
## Prop - inline
```typescript
defineProps({
inline: {
type: Boolean,
default: true,
},
})
```
<AlertInlineExample />
```vue
<script setup>
import { Alert } from 'flowbite-vue'
</script>
<template>
<div class="flex flex-col">
<Alert type="info" title="Info title" :inline="false" class="mb-2">
Lorem...
</Alert>
<Alert type="warning" title="Warning title" :inline="false" class="mb-2">
Lorem...
</Alert>
<Alert type="danger" title="Danger title" :inline="false" class="mb-2">
Lorem...
</Alert>
<Alert closable type="dark" title="Dark title" :inline="false" class="mb-2">
Lorem...
</Alert>
<Alert type="success" title="Success title" :inline="false">
Lorem...
<template #actions>
<Button size="sm" color="green" class="mr-2">
Buttons everywhere
</Button>
<Button size="sm" outline color="green">
<template #prefix>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M3 1a1 1 0 000 2h1.22l.305 1.222a.997.997 0 00.01.042l1.358 5.43-.893.892C3.74 11.846 4.632 14 6.414 14H15a1 1 0 000-2H6.414l1-1H14a1 1 0 00.894-.553l3-6A1 1 0 0017 3H6.28l-.31-1.243A1 1 0 005 1H3zM16 16.5a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0zM6.5 18a1.5 1.5 0 100-3 1.5 1.5 0 000 3z"></path></svg>
</template>
And here
</Button>
</template>
</Alert>
</div>
</template>
```

View File

@@ -0,0 +1,12 @@
<template>
<div class="vp-raw flex flex-col">
<Alert type="info" border class="mb-2">Info</Alert>
<Alert type="warning" border class="mb-2">Warning</Alert>
<Alert type="danger" border class="mb-2">Danger</Alert>
<Alert type="dark" border class="mb-2">Dark</Alert>
<Alert type="success" border>Success</Alert>
</div>
</template>
<script setup>
import { Alert } from '../../../../src/index'
</script>

View File

@@ -0,0 +1,12 @@
<template>
<div class="vp-raw flex flex-col">
<Alert type="info" closable class="mb-2">Info</Alert>
<Alert type="warning" closable class="mb-2">Warning</Alert>
<Alert type="danger" closable class="mb-2">Danger</Alert>
<Alert type="dark" closable class="mb-2">Dark</Alert>
<Alert type="success" closable>Success</Alert>
</div>
</template>
<script setup>
import { Alert } from '../../../../src/index'
</script>

View File

@@ -0,0 +1,12 @@
<template>
<div class="vp-raw flex flex-col">
<Alert type="info" :icon="false" class="mb-2">Info</Alert>
<Alert title="WARNING" type="warning" :icon="false" class="mb-2">Warning</Alert>
<Alert type="danger" :icon="false" class="mb-2">Danger</Alert>
<Alert type="dark" :icon="false" class="mb-2">Dark</Alert>
<Alert type="success" :icon="false">Success</Alert>
</div>
</template>
<script setup>
import { Alert } from '../../../../src/index'
</script>

View File

@@ -0,0 +1,33 @@
<template>
<div class="vp-raw flex flex-col">
<Alert type="info" title="Info title" :inline="false" class="mb-2">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Assumenda beatae eaque error est eveniet, facere illo labore libero minima molestiae neque nisi non officia quod sed temporibus unde vitae voluptates.
</Alert>
<Alert type="warning" title="Warning title" :inline="false" class="mb-2">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consequatur cupiditate dicta dolore dolores hic sequi tenetur, vero! Blanditiis consequatur culpa nisi ratione repellat! Delectus dolore magni nemo placeat qui sequi.
</Alert>
<Alert type="danger" title="Danger title" :inline="false" class="mb-2">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab accusamus accusantium atque cupiditate, dignissimos dolorum, error inventore iste libero minus nihil possimus quasi quia quibusdam quisquam recusandae repellat reprehenderit temporibus.
</Alert>
<Alert closable type="dark" title="Dark title" :inline="false" class="mb-2">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. A aperiam dolore et, fuga impedit, iusto nam numquam officiis quas, repellat sapiente sit unde vel? Adipisci delectus dolore eius optio sunt.
</Alert>
<Alert type="success" title="Success title" :inline="false">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias at culpa cupiditate deleniti eos ipsam ipsum, nostrum perspiciatis provident tempore? Aspernatur expedita praesentium voluptatibus. Accusamus explicabo iusto nobis reiciendis temporibus!
<template #actions>
<Button size="sm" color="green" class="mr-2">
Buttons everywhere
</Button>
<Button size="sm" outline color="green">
<template #prefix>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M3 1a1 1 0 000 2h1.22l.305 1.222a.997.997 0 00.01.042l1.358 5.43-.893.892C3.74 11.846 4.632 14 6.414 14H15a1 1 0 000-2H6.414l1-1H14a1 1 0 00.894-.553l3-6A1 1 0 0017 3H6.28l-.31-1.243A1 1 0 005 1H3zM16 16.5a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0zM6.5 18a1.5 1.5 0 100-3 1.5 1.5 0 000 3z"></path></svg>
</template>
And here
</Button>
</template>
</Alert>
</div>
</template>
<script setup>
import { Alert, Button } from '../../../../src/index'
</script>

View File

@@ -0,0 +1,12 @@
<template>
<div class="vp-raw flex flex-col">
<Alert type="info" title="Some info title" class="mb-2">Info</Alert>
<Alert type="warning" title="Some warning title" class="mb-2">Warning</Alert>
<Alert type="danger" title="Some danger title" class="mb-2">Danger</Alert>
<Alert type="dark" title="Some dark title" class="mb-2">Dark</Alert>
<Alert type="success" title="Some success title">Success</Alert>
</div>
</template>
<script setup>
import { Alert } from '../../../../src/index'
</script>

View File

@@ -0,0 +1,12 @@
<template>
<div class="vp-raw flex flex-col">
<Alert type="info" class="mb-2">Info</Alert>
<Alert type="warning" class="mb-2">Warning</Alert>
<Alert type="danger" class="mb-2">Danger</Alert>
<Alert type="dark" class="mb-2">Dark</Alert>
<Alert type="success">Success</Alert>
</div>
</template>
<script setup>
import { Alert } from '../../../../src/index'
</script>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="inline-flex align-center gap-2 flex-wrap"> <div class="vp-raw inline-flex align-center gap-2 flex-wrap">
<Button color="default">Default</Button> <Button color="default">Default</Button>
<Button color="alternative">Alternative</Button> <Button color="alternative">Alternative</Button>
<Button color="dark">Dark</Button> <Button color="dark">Dark</Button>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="inline-flex align-center gap-2 flex-wrap"> <div class="vp-raw inline-flex align-center gap-2 flex-wrap">
<Button color="default" disabled>Default</Button> <Button color="default" disabled>Default</Button>
<Button color="default" outline disabled>Default outline</Button> <Button color="default" outline disabled>Default outline</Button>
<Button gradient="red" disabled>Red gradient</Button> <Button gradient="red" disabled>Red gradient</Button>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="inline-flex align-center gap-2 flex-wrap"> <div class="vp-raw inline-flex align-center gap-2 flex-wrap">
<Button gradient="purple-blue">Purple to blue</Button> <Button gradient="purple-blue">Purple to blue</Button>
<Button gradient="cyan-blue">Cyan to blue</Button> <Button gradient="cyan-blue">Cyan to blue</Button>
<Button gradient="green-blue">Green to blue</Button> <Button gradient="green-blue">Green to blue</Button>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="inline-flex align-center gap-2 flex-wrap"> <div class="vp-raw inline-flex align-center gap-2 flex-wrap">
<Button gradient="blue">Blue</Button> <Button gradient="blue">Blue</Button>
<Button gradient="cyan">Cyan</Button> <Button gradient="cyan">Cyan</Button>
<Button gradient="green">Green</Button> <Button gradient="green">Green</Button>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="inline-flex align-center gap-2 flex-wrap"> <div class="vp-raw inline-flex align-center gap-2 flex-wrap">
<Button gradient="blue" shadow>Blue with blue</Button> <Button gradient="blue" shadow>Blue with blue</Button>
<Button gradient="cyan" shadow>Cyan with cyan</Button> <Button gradient="cyan" shadow>Cyan with cyan</Button>
<Button gradient="green" shadow>Green with green</Button> <Button gradient="green" shadow>Green with green</Button>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="inline-flex align-center gap-2 flex-wrap"> <div class="vp-raw inline-flex align-center gap-2 flex-wrap">
<Button gradient="purple-blue" square> <Button gradient="purple-blue" square>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
</Button> </Button>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="inline-flex items-center gap-2 flex-wrap"> <div class="vp-raw inline-flex items-center gap-2 flex-wrap">
<Button gradient="purple-blue" outline :disabled="loading" :loading="loading" @click="loading = !loading" size="xs"> <Button gradient="purple-blue" outline :disabled="loading" :loading="loading" @click="loading = !loading" size="xs">
Click me Click me
</Button> </Button>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="inline-flex align-center gap-2 flex-wrap"> <div class="vp-raw inline-flex align-center gap-2 flex-wrap">
<Button color="default" outline>Default</Button> <Button color="default" outline>Default</Button>
<Button color="dark" outline>Dark</Button> <Button color="dark" outline>Dark</Button>
<Button color="green" outline>Green</Button> <Button color="green" outline>Green</Button>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="inline-flex align-center gap-2 flex-wrap"> <div class="vp-raw inline-flex align-center gap-2 flex-wrap">
<Button gradient="purple-blue" outline>Purple to blue</Button> <Button gradient="purple-blue" outline>Purple to blue</Button>
<Button gradient="cyan-blue" outline>Cyan to blue</Button> <Button gradient="cyan-blue" outline>Cyan to blue</Button>
<Button gradient="green-blue" outline>Green to blue</Button> <Button gradient="green-blue" outline>Green to blue</Button>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="inline-flex align-center gap-2 flex-wrap"> <div class="vp-raw inline-flex align-center gap-2 flex-wrap">
<Button color="default" pill>Default</Button> <Button color="default" pill>Default</Button>
<Button color="alternative" pill>Alternative</Button> <Button color="alternative" pill>Alternative</Button>
<Button color="dark" pill>Dark</Button> <Button color="dark" pill>Dark</Button>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="inline-flex align-center gap-2 flex-wrap"> <div class="vp-raw inline-flex align-center gap-2 flex-wrap">
<Button color="default"> <Button color="default">
<template #prefix> <template #prefix>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M3 1a1 1 0 000 2h1.22l.305 1.222a.997.997 0 00.01.042l1.358 5.43-.893.892C3.74 11.846 4.632 14 6.414 14H15a1 1 0 000-2H6.414l1-1H14a1 1 0 00.894-.553l3-6A1 1 0 0017 3H6.28l-.31-1.243A1 1 0 005 1H3zM16 16.5a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0zM6.5 18a1.5 1.5 0 100-3 1.5 1.5 0 000 3z"></path></svg> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M3 1a1 1 0 000 2h1.22l.305 1.222a.997.997 0 00.01.042l1.358 5.43-.893.892C3.74 11.846 4.632 14 6.414 14H15a1 1 0 000-2H6.414l1-1H14a1 1 0 00.894-.553l3-6A1 1 0 0017 3H6.28l-.31-1.243A1 1 0 005 1H3zM16 16.5a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0zM6.5 18a1.5 1.5 0 100-3 1.5 1.5 0 000 3z"></path></svg>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="inline-flex items-center gap-2 flex-wrap"> <div class="vp-raw inline-flex items-center gap-2 flex-wrap">
<Button size="xs">Extra small - xs</Button> <Button size="xs">Extra small - xs</Button>
<Button size="sm">Small - sm</Button> <Button size="sm">Small - sm</Button>
<Button size="md">Medium - md</Button> <Button size="md">Medium - md</Button>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="inline-flex align-center gap-2 flex-wrap"> <div class="vp-raw inline-flex align-center gap-2 flex-wrap">
<Button gradient="red-yellow" square> <Button gradient="red-yellow" square>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
</Button> </Button>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="inline-flex align-center gap-2 flex-wrap"> <div class="vp-raw inline-flex align-center gap-2 flex-wrap">
<Button color="default"> <Button color="default">
Choose plan Choose plan
<template #suffix> <template #suffix>

View File

@@ -1,10 +1,12 @@
<template> <template>
<button-group> <div class="vp-raw">
<Button>hello world</Button> <button-group>
<Button color="purple">hello world</Button> <Button>hello world</Button>
<Button color="alternative">hello world</Button> <Button color="purple">hello world</Button>
<Button color="red">hello world</Button> <Button color="alternative">hello world</Button>
</button-group> <Button color="red">hello world</Button>
</button-group>
</div>
</template> </template>
<script setup> <script setup>
import { ButtonGroup, Button } from '../../../../src/index' import { ButtonGroup, Button } from '../../../../src/index'

View File

@@ -1,15 +1,17 @@
<template> <template>
<button-group> <div class="vp-raw">
<Button outline>Button1</Button> <button-group>
<Button outline>Button2</Button> <Button outline>Button1</Button>
<Button outline>Button3</Button> <Button outline>Button2</Button>
<Button outline> <Button outline>Button3</Button>
hello world <Button outline>
<template #suffix> hello world
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg> <template #suffix>
</template> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
</Button> </template>
</button-group> </Button>
</button-group>
</div>
</template> </template>
<script setup> <script setup>
import { ButtonGroup, Button } from '../../../../src/index' import { ButtonGroup, Button } from '../../../../src/index'

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="inline-flex align-center gap-2 flex-wrap"> <div class="vp-raw inline-flex align-center gap-2 flex-wrap">
<spinner /> <spinner />
</div> </div>
</template> </template>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="inline-flex align-center gap-2 flex-wrap"> <div class="vp-raw inline-flex align-center gap-2 flex-wrap">
<spinner color="blue" size="6" /> <spinner color="blue" size="6" />
<spinner color="pink" size="8" /> <spinner color="pink" size="8" />
<spinner color="gray" size="10" /> <spinner color="gray" size="10" />

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="inline-flex align-center gap-2 flex-wrap"> <div class="vp-raw inline-flex align-center gap-2 flex-wrap">
<spinner /> <spinner />
<spinner size="6" /> <spinner size="6" />
<spinner size="8" /> <spinner size="8" />

View File

@@ -0,0 +1,23 @@
<template>
<div class="vp-raw">
<tabs v-model="activeTab" class="p-5">
<tab name="first" title="First">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Explicabo, id, nisi? Enim excepturi id minus molestias quaerat qui rem repudiandae sed tempore ullam voluptate, voluptatum. Consequuntur illum possimus tempora totam.
</tab>
<tab name="second" title="Second">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Assumenda consectetur expedita explicabo facere facilis fugit illo laboriosam minus molestias nulla placeat porro quaerat, quo repellat sapiente similique temporibus voluptate. Nemo!
</tab>
<tab name="third" title="Third">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus alias assumenda consequatur, dicta eius eos excepturi hic magnam maxime molestias nisi perferendis provident quia. Aliquam consequatur esse ex sit velit.
</tab>
<tab name="fourth" title="Fourth" :disabled="true">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Commodi deleniti dolores ea eligendi quis, ratione repellat temporibus veniam veritatis voluptates. Distinctio enim eos illo incidunt ipsam provident, quaerat quia vel!
</tab>
</tabs>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { Tabs, Tab } from '../../../../src/index'
const activeTab = ref('first')
</script>

View File

@@ -0,0 +1,23 @@
<template>
<div class="vp-raw">
<tabs variant="pills" v-model="activeTab" class="p-5">
<tab name="first" title="First">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab aspernatur debitis iste libero molestiae mollitia, optio sunt? A, consectetur distinctio, eaque harum iusto laudantium, molestiae nam odio officia pariatur vitae?
</tab>
<tab name="second" title="Second">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam asperiores autem cupiditate, deleniti eligendi exercitationem magnam maiores, minus pariatur provident quasi qui quidem recusandae rem reprehenderit sapiente sequi sint soluta.
</tab>
<tab name="third" title="Third">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam animi aperiam assumenda consectetur, dolorem, dolores, ea eos ipsum itaque iure laudantium nostrum nulla numquam perspiciatis provident qui quod totam voluptatem.
</tab>
<tab name="fourth" title="Fourth" :disabled="true">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto blanditiis cupiditate ea error eveniet hic impedit in labore maxime, minima mollitia nam sapiente sint tempora tempore vel velit veniam, voluptatem.
</tab>
</tabs>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { Tabs, Tab } from '../../../../src/index'
const activeTab = ref('first')
</script>

View File

@@ -0,0 +1,23 @@
<template>
<div class="vp-raw">
<tabs variant="underline" v-model="activeTab" class="p-5">
<tab name="first" title="First">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab aspernatur debitis iste libero molestiae mollitia, optio sunt? A, consectetur distinctio, eaque harum iusto laudantium, molestiae nam odio officia pariatur vitae?
</tab>
<tab name="second" title="Second">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam asperiores autem cupiditate, deleniti eligendi exercitationem magnam maiores, minus pariatur provident quasi qui quidem recusandae rem reprehenderit sapiente sequi sint soluta.
</tab>
<tab name="third" title="Third">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam animi aperiam assumenda consectetur, dolorem, dolores, ea eos ipsum itaque iure laudantium nostrum nulla numquam perspiciatis provident qui quod totam voluptatem.
</tab>
<tab name="fourth" title="Fourth" :disabled="true">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto blanditiis cupiditate ea error eveniet hic impedit in labore maxime, minima mollitia nam sapiente sint tempora tempore vel velit veniam, voluptatem.
</tab>
</tabs>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { Tabs, Tab } from '../../../../src/index'
const activeTab = ref('first')
</script>

116
docs/guide/tabs/tabs.md Normal file
View File

@@ -0,0 +1,116 @@
<script setup>
import TabsDefaultExample from './examples/TabsDefaultExample.vue';
import TabsPillsExample from './examples/TabsPillsExample.vue';
import TabsUnderlineExample from './examples/TabsUnderlineExample.vue';
</script>
## Prop - variant (default)
```typescript
export type TabsVariant = 'default' | 'underline' | 'pills'
defineProps({
variant: {
type: String as PropType<TabsVariant>,
default: 'default',
},
})
```
<TabsDefaultExample />
```vue
<script setup>
import { ref } from 'vue'
import { Tabs, Tab } from 'flowbite-vue'
const activeTab = ref('first')
</script>
<template>
<tabs v-model="activeTab" class="p-5"> <!-- class appends to content DIV for all tabs -->
<tab name="first" title="First">
Lorem...
</tab>
<tab name="second" title="Second">
Lorem...
</tab>
<tab name="third" title="Third">
Lorem...
</tab>
<tab name="fourth" title="Fourth" :disabled="true">
Lorem...
</tab>
</tabs>
</template>
```
## Prop - variant (underline)
<TabsUnderlineExample />
```vue
<script setup>
import { ref } from 'vue'
import { Tabs, Tab } from 'flowbite-vue'
const activeTab = ref('first')
</script>
<template>
<tabs variant="underline" v-model="activeTab" class="p-5"> <!-- class appends to content DIV for all tabs -->
<tab name="first" title="First">
Lorem...
</tab>
<tab name="second" title="Second">
Lorem...
</tab>
<tab name="third" title="Third">
Lorem...
</tab>
<tab name="fourth" title="Fourth" :disabled="true">
Lorem...
</tab>
</tabs>
</template>
```
## Prop - variant (pills)
<TabsPillsExample />
```vue
<script setup>
import { ref } from 'vue'
import { Tabs, Tab } from 'flowbite-vue'
const activeTab = ref('first')
</script>
<template>
<tabs variant="pills" v-model="activeTab" class="p-5"> <!-- class appends to content DIV for all tabs -->
<tab name="first" title="First">
Lorem...
</tab>
<tab name="second" title="Second">
Lorem...
</tab>
<tab name="third" title="Third">
Lorem...
</tab>
<tab name="fourth" title="Fourth" :disabled="true">
Lorem...
</tab>
</tabs>
</template>
```
## Prop - directive
Use this props if you want to control which directive to use for rendering every tab content
```typescript
export type TabsVariant = 'default' | 'underline' | 'pills'
defineProps({
directive: {
type: String as PropType<'if' | 'show'>,
default: 'if',
},
})
```

6
package-lock.json generated
View File

@@ -2357,6 +2357,12 @@
"postcss-selector-parser": "^6.0.6" "postcss-selector-parser": "^6.0.6"
} }
}, },
"postcss-prefix-selector": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/postcss-prefix-selector/-/postcss-prefix-selector-1.16.0.tgz",
"integrity": "sha512-rdVMIi7Q4B0XbXqNUEI+Z4E+pueiu/CS5E6vRCQommzdQ/sgsS4dK42U7GX8oJR+TJOtT+Qv3GkNo6iijUMp3Q==",
"dev": true
},
"postcss-selector-parser": { "postcss-selector-parser": {
"version": "6.0.10", "version": "6.0.10",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",

View File

@@ -44,7 +44,8 @@
"eslint-plugin-vue": "^9.1.1", "eslint-plugin-vue": "^9.1.1",
"flowbite": "^1.4.2", "flowbite": "^1.4.2",
"jsdom": "^20.0.0", "jsdom": "^20.0.0",
"postcss": "^8.4.6", "postcss": "^8.4.14",
"postcss-prefix-selector": "^1.16.0",
"prettier": "^2.3.2", "prettier": "^2.3.2",
"tailwindcss": "^3.1.4", "tailwindcss": "^3.1.4",
"typescript": "^4.7.3", "typescript": "^4.7.3",
@@ -53,10 +54,5 @@
"vitest": "^0.16.0", "vitest": "^0.16.0",
"vue-eslint-parser": "^9.0.3", "vue-eslint-parser": "^9.0.3",
"vue-tsc": "^0.38.2" "vue-tsc": "^0.38.2"
},
"postcss": {
"plugins": {
"tailwindcss": {}
}
} }
} }

View File

@@ -0,0 +1,73 @@
<template>
<div :class="alertClasses" role="alert" v-if="visible">
<div :class="inline ? 'flex' : 'flex items-center'">
<svg v-if="icon" :class="['flex-shrink-0', 'w-5', 'h-5', textClasses]" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
<span
:class="titleClasses"
v-if="title"
>
{{ title }}
</span>
<button type="button" :class="closeClasses" aria-label="Close" @click="onCloseClick" v-if="!inline && closable">
<span class="sr-only">Dismiss</span>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
</button>
</div>
<div :class="contentClasses">
<slot />
</div>
<div class="inline-flex items-center" v-if="$slots.actions">
<slot name="actions" />
</div>
<button type="button" :class="closeClasses" aria-label="Close" @click="onCloseClick" v-if="inline && closable">
<span class="sr-only">Dismiss</span>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
</button>
</div>
</template>
<script lang="ts" setup>
import type { PropType } from 'vue'
import { useAlertClasses } from './useAlertClasses'
import { onBeforeUnmount, ref, toRefs } from 'vue'
export type AlertType = 'info' | 'danger' | 'success' | 'warning' | 'dark'
const props = defineProps({
type: {
type: String as PropType<AlertType>,
default: 'info',
},
title: {
type: String,
default: '',
},
closable: {
type: Boolean,
default: false,
},
icon: {
type: Boolean,
default: true,
},
border: {
type: Boolean,
default: false,
},
inline: {
type: Boolean,
default: true,
},
})
const { alertClasses, textClasses, closeClasses, contentClasses, titleClasses } = useAlertClasses(toRefs(props))
const visible = ref(true)
const onCloseClick = () => {
visible.value = false
}
onBeforeUnmount(() => {
console.log('UNMOUNTED')
})
</script>

View File

@@ -0,0 +1,106 @@
import type { AlertType } from './Alert.vue'
import { computed } from 'vue'
import type { Ref } from 'vue'
import classNames from 'classnames'
const defaultAlertClasses = 'p-4 text-sm'
const alertTextClasses: Record<AlertType, string> = {
danger: 'text-red-700 dark:text-red-800',
dark: 'text-gray-700 dark:text-gray-300',
info: 'text-blue-700 dark:text-blue-800',
success: 'text-green-700 dark:text-green-800',
warning: 'text-yellow-700 dark:text-yellow-800',
}
const alertTypeClasses: Record<AlertType, string> = {
danger: 'bg-red-100 dark:bg-red-200',
dark: 'bg-gray-100 dark:bg-gray-700',
info: 'bg-blue-100 dark:bg-blue-200',
success: 'bg-green-100 dark:bg-green-200',
warning: 'bg-yellow-100 dark:bg-yellow-200',
}
const alertBorderClasses: Record<AlertType, string> = {
danger: 'border-t-4 border-red-500',
dark: 'border-t-4 border-gray-500',
info: 'border-t-4 border-blue-500',
success: 'border-t-4 border-green-500',
warning: 'border-t-4 border-yellow-500',
}
const defaultCloseButtonClasses = 'ml-auto -mx-1.5 -my-1.5 rounded-lg focus:ring-2 p-1.5 inline-flex h-8 w-8'
const closeButtonClasses: Record<AlertType, string> = {
danger: 'hover:bg-red-200 dark:hover:bg-red-300 focus:ring-red-400 bg-red-100 dark:bg-red-200 text-red-500',
dark: 'dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-white hover:bg-gray-200 focus:ring-gray-400 bg-gray-100 text-gray-500',
info: 'hover:bg-blue-200 dark:hover:bg-blue-300 focus:ring-blue-400 bg-blue-100 dark:bg-blue-200 text-blue-500',
success: 'bg-green-100 dark:bg-green-200 text-green-500 focus:ring-green-400 hover:bg-green-200 dark:hover:bg-green-300',
warning: 'focus:ring-yellow-400 hover:bg-yellow-200 dark:hover:bg-yellow-300 bg-yellow-100 dark:bg-yellow-200 text-yellow-500',
}
export type UseAlertClassesProps = {
type: Ref<AlertType>
border: Ref<boolean>
icon: Ref<boolean>
inline: Ref<boolean>
title: Ref<string>
}
export function useAlertClasses(props: UseAlertClassesProps): {
alertClasses: Ref<string>,
textClasses: Ref<string>,
closeClasses: Ref<string>,
contentClasses: Ref<string>,
titleClasses: Ref<string>,
} {
const alertClasses = computed<string>(() => {
return classNames(
defaultAlertClasses,
alertTypeClasses[props.type.value],
textClasses.value,
props.border.value ? alertBorderClasses[props.type.value] : 'rounded-lg', // rounded only if no border
props.inline.value ? 'flex' : '',
)
})
const textClasses = computed<string>(() => {
return classNames(
alertTextClasses[props.type.value],
)
})
const closeClasses = computed<string>(() => {
return classNames(
defaultCloseButtonClasses,
closeButtonClasses[props.type.value],
)
})
const contentClasses = computed<string>(() => {
if(!props.inline.value) return classNames('mt-2 mb-4')
if(!props.icon.value && !props.title.value) return ''
return classNames(!props.title.value ? 'ml-3' : 'ml-1')
})
const titleClasses = computed<string>(() => {
if(!props.icon.value || !props.inline.value) return classNames(
'font-medium',
!props.inline.value ? 'text-lg ml-2' : '',
)
return classNames(
'font-medium ml-3',
!props.inline.value ? 'text-lg' : '',
)
})
return {
alertClasses,
textClasses,
closeClasses,
contentClasses,
titleClasses,
}
}

View File

@@ -28,7 +28,7 @@
</button> </button>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue' import { computed, toRefs } from 'vue'
import type { PropType } from 'vue' import type { PropType } from 'vue'
import Spinner from '../Spinner/Spinner.vue' import Spinner from '../Spinner/Spinner.vue'
import { useButtonClasses } from './useButtonClasses' import { useButtonClasses } from './useButtonClasses'
@@ -89,7 +89,7 @@ const isOutlineGradient = computed(() => props.outline && props.gradient)
const loadingPrefix = computed(() => props.loading && props.loadingPosition === 'prefix') const loadingPrefix = computed(() => props.loading && props.loadingPosition === 'prefix')
const loadingSuffix = computed(() => props.loading && props.loadingPosition === 'suffix') const loadingSuffix = computed(() => props.loading && props.loadingPosition === 'suffix')
const { wrapperClasses, spanClasses } = useButtonClasses(props) const { wrapperClasses, spanClasses } = useButtonClasses(toRefs(props))
const { color: spinnerColor, size: spinnerSize } = useButtonSpinner(props) const { color: spinnerColor, size: spinnerSize } = useButtonSpinner(toRefs(props))
</script> </script>

View File

@@ -4,7 +4,9 @@ import classNames from 'classnames'
import type { ButtonDuotoneGradient, ButtonGradient, ButtonMonochromeGradient, ButtonSize, ButtonVariant } from './Button.vue' import type { ButtonDuotoneGradient, ButtonGradient, ButtonMonochromeGradient, ButtonSize, ButtonVariant } from './Button.vue'
const buttonColorClasses: { hover: Record<ButtonVariant, string>, default: Record<ButtonVariant, string> } = { export type ButtonClassMap<T extends string> = { hover: Record<T, string>, default: Record<T, string> }
const buttonColorClasses: ButtonClassMap<ButtonVariant> = {
default: { default: {
default: 'text-white bg-blue-700 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg dark:bg-blue-600 focus:outline-none dark:focus:ring-blue-800', default: 'text-white bg-blue-700 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg dark:bg-blue-600 focus:outline-none dark:focus:ring-blue-800',
alternative: 'font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 focus:z-10 focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600', alternative: 'font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 focus:z-10 focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600',
@@ -28,10 +30,7 @@ const buttonColorClasses: { hover: Record<ButtonVariant, string>, default: Reco
}, },
} }
const buttonOutlineColorClasses: { const buttonOutlineColorClasses: ButtonClassMap<Exclude<ButtonVariant, 'light' | 'alternative'>> = {
hover: Record<Exclude<ButtonVariant, 'light' | 'alternative'>, string>,
default: Record<Exclude<ButtonVariant, 'light' | 'alternative'>, string>
} = {
default: { default: {
dark: 'text-gray-900 border border-gray-800 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-lg text-sm text-center dark:border-gray-600 dark:text-gray-400 dark:focus:ring-gray-800', dark: 'text-gray-900 border border-gray-800 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-lg text-sm text-center dark:border-gray-600 dark:text-gray-400 dark:focus:ring-gray-800',
default: 'text-blue-700 border border-blue-700 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm text-center dark:border-blue-500 dark:text-blue-500 dark:focus:ring-blue-800', default: 'text-blue-700 border border-blue-700 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm text-center dark:border-blue-500 dark:text-blue-500 dark:focus:ring-blue-800',
@@ -51,7 +50,7 @@ const buttonOutlineColorClasses: {
} }
const buttonGradientClasses: { hover: Record<ButtonGradient, string>, default: Record<ButtonGradient, string> } = { const buttonGradientClasses: ButtonClassMap<ButtonGradient> = {
hover: { hover: {
'cyan-blue': 'hover:bg-gradient-to-bl', 'cyan-blue': 'hover:bg-gradient-to-bl',
'green-blue': 'hover:bg-gradient-to-bl', 'green-blue': 'hover:bg-gradient-to-bl',
@@ -88,7 +87,7 @@ const buttonGradientClasses: { hover: Record<ButtonGradient, string>, default: R
}, },
} }
const buttonOutlineGradientClasses: { hover: Record<ButtonDuotoneGradient, string>, default: Record<ButtonDuotoneGradient, string> } = { const buttonOutlineGradientClasses: ButtonClassMap<ButtonDuotoneGradient> = {
default: { default: {
'cyan-blue': 'cyan-blue':
'relative inline-flex items-center justify-center overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-cyan-500 to-blue-500 dark:text-white focus:ring-4 focus:outline-none focus:ring-cyan-200 dark:focus:ring-cyan-800', 'relative inline-flex items-center justify-center overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-cyan-500 to-blue-500 dark:text-white focus:ring-4 focus:outline-none focus:ring-cyan-200 dark:focus:ring-cyan-800',
@@ -145,15 +144,15 @@ const buttonShadowClasses: Record<ButtonMonochromeGradient, string> = {
} }
export type UseButtonClassesProps = { export type UseButtonClassesProps = {
pill: boolean pill: Ref<boolean>
disabled: boolean disabled: Ref<boolean>
loading: boolean loading: Ref<boolean>
outline: boolean outline: Ref<boolean>
size: ButtonSize size: Ref<ButtonSize>
square: boolean square: Ref<boolean>
color: ButtonVariant color: Ref<ButtonVariant>
gradient: ButtonGradient | null gradient: Ref<ButtonGradient | null>
shadow: ButtonMonochromeGradient | '' | null shadow: Ref<ButtonMonochromeGradient | '' | null>
} }
const simpleGradients = ['blue', 'green', 'cyan', 'teal', 'lime', 'red', 'pink', 'purple'] const simpleGradients = ['blue', 'green', 'cyan', 'teal', 'lime', 'red', 'pink', 'purple']
@@ -163,64 +162,64 @@ export function useButtonClasses(props: UseButtonClassesProps): { wrapperClasses
const slots = useSlots() const slots = useSlots()
const sizeClasses = computed(() => { const sizeClasses = computed(() => {
if (props.square) return buttonSquareSizeClasses[props.size] if (props.square.value) return buttonSquareSizeClasses[props.size.value]
return buttonSizeClasses[props.size] return buttonSizeClasses[props.size.value]
}) })
const bindClasses = computed(() => { const bindClasses = computed(() => {
const isGradient = !!props.gradient const isGradient = !!props.gradient.value
const isColor = !!props.color const isColor = !!props.color.value
const isOutline = props.outline const isOutline = props.outline.value
let hoverClass = '' let hoverClass = ''
let backgroundClass = '' let backgroundClass = ''
if (isGradient && isOutline) { // GRADIENT AND OUTLINE if (isGradient && isOutline) { // GRADIENT AND OUTLINE
if (!simpleGradients.includes(props.gradient!)) { if (!simpleGradients.includes(props.gradient.value!)) {
backgroundClass = buttonOutlineGradientClasses.default[props.gradient as unknown as keyof typeof buttonOutlineGradientClasses.default] backgroundClass = buttonOutlineGradientClasses.default[props.gradient.value as unknown as keyof typeof buttonOutlineGradientClasses.default]
if(!props.disabled) if(!props.disabled.value)
hoverClass = buttonOutlineGradientClasses.hover[props.gradient as unknown as keyof typeof buttonOutlineGradientClasses.hover] hoverClass = buttonOutlineGradientClasses.hover[props.gradient.value as unknown as keyof typeof buttonOutlineGradientClasses.hover]
} else { } else {
console.warn(`cannot use outline prop with "${props.gradient}" gradient`) // TODO: prettify console.warn(`cannot use outline prop with "${props.gradient.value}" gradient`) // TODO: prettify
} }
} else if (isGradient) { // JUST GRADIENT } else if (isGradient) { // JUST GRADIENT
backgroundClass = buttonGradientClasses.default[props.gradient!] backgroundClass = buttonGradientClasses.default[props.gradient.value!]
if(!props.disabled) if(!props.disabled.value)
hoverClass = buttonGradientClasses.hover[props.gradient!] hoverClass = buttonGradientClasses.hover[props.gradient.value!]
} else if (isColor && isOutline) { // COLOR AND OUTLINE } else if (isColor && isOutline) { // COLOR AND OUTLINE
if (!alternativeColors.includes(props.color)) { if (!alternativeColors.includes(props.color.value)) {
backgroundClass = buttonOutlineColorClasses.default[props.color as unknown as keyof typeof buttonOutlineColorClasses.default] backgroundClass = buttonOutlineColorClasses.default[props.color.value as unknown as keyof typeof buttonOutlineColorClasses.default]
if(!props.disabled) if(!props.disabled.value)
hoverClass = buttonOutlineColorClasses.hover[props.color as unknown as keyof typeof buttonOutlineColorClasses.hover] hoverClass = buttonOutlineColorClasses.hover[props.color.value as unknown as keyof typeof buttonOutlineColorClasses.hover]
} else { } else {
console.warn(`cannot use outline prop with "${props.color}" color`) // TODO: prettify console.warn(`cannot use outline prop with "${props.color.value}" color`) // TODO: prettify
} }
} else { // JUST COLOR } else { // JUST COLOR
backgroundClass = buttonColorClasses.default[props.color] backgroundClass = buttonColorClasses.default[props.color.value]
if(!props.disabled) if(!props.disabled.value)
hoverClass = buttonColorClasses.hover[props.color] hoverClass = buttonColorClasses.hover[props.color.value]
} }
let shadowClass = '' let shadowClass = ''
if (props.shadow === '') { if (props.shadow.value === '') {
// if shadow prop passed without value - try to find color for shadow by gradient // if shadow prop passed without value - try to find color for shadow by gradient
if (props.gradient && simpleGradients.includes(props.gradient)) { if (props.gradient.value && simpleGradients.includes(props.gradient.value!)) {
shadowClass = buttonShadowClasses[props.gradient as unknown as keyof typeof buttonShadowClasses] shadowClass = buttonShadowClasses[props.gradient.value as unknown as keyof typeof buttonShadowClasses]
} }
} else if (typeof props.shadow === 'string') { } else if (typeof props.shadow.value === 'string') {
// if provided color for shadow - use it // if provided color for shadow - use it
if (simpleGradients.includes(props.shadow)) { if (simpleGradients.includes(props.shadow.value)) {
shadowClass = buttonShadowClasses[props.shadow as unknown as keyof typeof buttonShadowClasses] shadowClass = buttonShadowClasses[props.shadow.value as unknown as keyof typeof buttonShadowClasses]
} }
} }
@@ -228,19 +227,19 @@ export function useButtonClasses(props: UseButtonClassesProps): { wrapperClasses
backgroundClass, backgroundClass,
hoverClass, hoverClass,
shadowClass, shadowClass,
props.pill ? '!rounded-full' : '', props.pill.value ? '!rounded-full' : '',
props.disabled ? 'cursor-not-allowed opacity-50' : '', props.disabled.value ? 'cursor-not-allowed opacity-50' : '',
(isGradient && isOutline) ? 'p-0.5' : sizeClasses.value, (isGradient && isOutline) ? 'p-0.5' : sizeClasses.value,
(slots.prefix || slots.suffix || props.loading) ? 'inline-flex items-center' : '', (slots.prefix || slots.suffix || props.loading.value) ? 'inline-flex items-center' : '',
) )
}) })
const spanClasses = computed(() => { const spanClasses = computed(() => {
if (!!props.gradient && props.outline) { // ONLY FOR GRADIENT OUTLINE BUTTON if (!!props.gradient.value && props.outline.value) { // ONLY FOR GRADIENT OUTLINE BUTTON
return classNames( return classNames(
'relative bg-white dark:bg-gray-900 rounded-md inline-flex items-center', 'relative bg-white dark:bg-gray-900 rounded-md inline-flex items-center',
sizeClasses.value, sizeClasses.value,
!props.disabled ? 'group-hover:bg-opacity-0 transition-all ease-in duration-75' : '', !props.disabled.value ? 'group-hover:bg-opacity-0 transition-all ease-in duration-75' : '',
) )
} }
return '' return ''

View File

@@ -1,13 +1,13 @@
import type {ButtonGradient, ButtonSize, ButtonVariant} from './Button.vue' import type { ButtonGradient, ButtonSize, ButtonVariant } from './Button.vue'
import type {SpinnerColor, SpinnerSize} from '../Spinner/Spinner.vue' import type { SpinnerColor, SpinnerSize } from '../Spinner/Spinner.vue'
import type {Ref} from 'vue' import type { Ref } from 'vue'
import {computed} from 'vue' import { computed } from 'vue'
export type UseButtonSpinnerProps = { export type UseButtonSpinnerProps = {
outline: boolean outline: Ref<boolean>
size: ButtonSize size: Ref<ButtonSize>
color: ButtonVariant color: Ref<ButtonVariant>
gradient: ButtonGradient | null gradient: Ref<ButtonGradient | null>
} }
export function useButtonSpinner(props: UseButtonSpinnerProps): { size: Ref<SpinnerSize>, color: Ref<SpinnerColor> } { export function useButtonSpinner(props: UseButtonSpinnerProps): { size: Ref<SpinnerSize>, color: Ref<SpinnerColor> } {
@@ -15,26 +15,26 @@ export function useButtonSpinner(props: UseButtonSpinnerProps): { size: Ref<Spin
lg: '5', md: '4', sm: '3', xl: '6', xs: '2.5', lg: '5', md: '4', sm: '3', xl: '6', xs: '2.5',
} }
const size = computed<SpinnerSize>(() => { const size = computed<SpinnerSize>(() => {
return btnSizeSpinnerSizeMap[props.size] return btnSizeSpinnerSizeMap[props.size.value]
}) })
const color = computed<SpinnerColor>(() => { const color = computed<SpinnerColor>(() => {
if(!props.outline) return 'white' if(!props.outline.value) return 'white'
if(props.gradient) { if(props.gradient.value) {
if(props.gradient.includes('purple')) return 'purple' if(props.gradient.value.includes('purple')) return 'purple'
else if(props.gradient.includes('blue')) return 'blue' else if(props.gradient.value.includes('blue')) return 'blue'
else if(props.gradient.includes('pink')) return 'pink' else if(props.gradient.value.includes('pink')) return 'pink'
else if(props.gradient.includes('red')) return 'red' else if(props.gradient.value.includes('red')) return 'red'
return 'white' return 'white'
} }
if(['alternative', 'dark', 'light'].includes(props.color)) { if(['alternative', 'dark', 'light'].includes(props.color.value)) {
return 'white' return 'white'
} else if(props.color === 'default') { } else if(props.color.value === 'default') {
return 'blue' return 'blue'
} }
return props.color as SpinnerColor return props.color.value as SpinnerColor
}) })
return { return {

View File

@@ -7,6 +7,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { useSpinnerClasses } from './useSpinnerClasses' import { useSpinnerClasses } from './useSpinnerClasses'
import { toRefs } from 'vue'
export type SpinnerSize = '0' | 'px' | '0.5' | '1' | '1.5' | '2' | '2.5' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12' export type SpinnerSize = '0' | 'px' | '0.5' | '1' | '1.5' | '2' | '2.5' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12'
export type SpinnerColor = 'blue' | 'gray' | 'green' | 'red' | 'yellow' | 'pink' | 'purple' | 'white' export type SpinnerColor = 'blue' | 'gray' | 'green' | 'red' | 'yellow' | 'pink' | 'purple' | 'white'
@@ -22,5 +23,5 @@ const props = defineProps({
}, },
}) })
const { spinnerClasses } = useSpinnerClasses(props) const { spinnerClasses } = useSpinnerClasses(toRefs(props))
</script> </script>

View File

@@ -35,14 +35,14 @@ const colors: Record<SpinnerColor, string> = {
} }
export type UseSpinnerClassesProps = { export type UseSpinnerClassesProps = {
size: SpinnerSize size: Ref<SpinnerSize>
color: SpinnerColor color: Ref<SpinnerColor>
} }
export function useSpinnerClasses(props: UseSpinnerClassesProps): { spinnerClasses: Ref<string> } { export function useSpinnerClasses(props: UseSpinnerClassesProps): { spinnerClasses: Ref<string> } {
const sizeClasses = computed(() => sizes[props.size]) const sizeClasses = computed(() => sizes[props.size.value])
const colorClasses = computed(() => colors[props.color]) const colorClasses = computed(() => colors[props.color.value])
const bgColorClasses = computed(() => 'text-gray-200 dark:text-gray-600') const bgColorClasses = computed(() => 'text-gray-200 dark:text-gray-600')
const animateClasses = computed(() => 'animate-spin') const animateClasses = computed(() => 'animate-spin')

View File

@@ -0,0 +1,87 @@
<template>
<div>
<div :class="divClasses">
<ul :class="ulClasses">
<tab-pane
v-for="(item, id) in tabsChildren"
:key="id"
:active="modelValueRef === item.props.name"
:name="item.props.name"
:disabled="item.props.disabled"
:title="item.props.title"
/>
</ul>
</div>
<div v-bind="$attrs">
<slot />
</div>
</div>
</template>
<script lang="ts" setup>
import {
TAB_ACTIVATE_INJECTION_KEY,
TAB_ACTIVE_NAME_INJECTION_KEY,
TAB_STYLE_INJECTION_KEY,
TAB_VISIBILITY_DIRECTIVE_INJECTION_KEY,
} from './config'
import { useTabsClasses } from './useTabsClasses'
import type { PropType } from 'vue'
import { computed, provide, toRef, useSlots } from 'vue'
import { flatten } from '../../utils/flatten'
import TabPane from './components/TabPane/TabPane.vue'
const props = defineProps({
variant: {
type: String as PropType<TabsVariant>,
default: 'default',
},
modelValue: {
type: String,
default: '',
},
directive: {
type: String as PropType<'if' | 'show'>,
default: 'if',
},
})
const emit = defineEmits(['update:modelValue'])
const { ulClasses, divClasses } = useTabsClasses(props)
provide(TAB_STYLE_INJECTION_KEY, props.variant)
const slots = useSlots()
const defaultSlot = slots.default
const tabsChildren = computed(() => {
return defaultSlot
? flatten(defaultSlot()).filter((v) => {
return (v.type as { __FLOWBITE_TAB__?: true }).__FLOWBITE_TAB__
})
: []
})
const modelValueRef = computed({
get: () => props.modelValue,
set: (value: string) => emit('update:modelValue', value),
})
provide(TAB_ACTIVE_NAME_INJECTION_KEY, modelValueRef)
provide(TAB_VISIBILITY_DIRECTIVE_INJECTION_KEY, toRef(props, 'directive'))
const onActivate = (value: string) => {
modelValueRef.value = value
}
provide(TAB_ACTIVATE_INJECTION_KEY, onActivate)
</script>
<script lang="ts">
export default {
inheritAttrs: false,
}
export type TabsVariant = 'default' | 'underline' | 'pills'
</script>

View File

@@ -0,0 +1,42 @@
<template>
<div>
<template v-if="directive === 'if'">
<div v-if="activeTab === name">
<slot/>
</div>
</template>
<template v-else-if="directive === 'show'">
<div v-show="activeTab === name">
<slot/>
</div>
</template>
</div>
</template>
<script lang="ts" setup>
import { inject } from 'vue'
import { TAB_ACTIVE_NAME_INJECTION_KEY, TAB_VISIBILITY_DIRECTIVE_INJECTION_KEY } from '../../config'
defineProps({
name: {
type: String,
required: true,
},
title: {
type: String,
default: '',
},
disabled: {
type: Boolean,
default: false,
},
})
const activeTab = inject(TAB_ACTIVE_NAME_INJECTION_KEY, '')
const directive = inject(TAB_VISIBILITY_DIRECTIVE_INJECTION_KEY, 'if')
</script>
<script lang="ts">
export default {
__FLOWBITE_TAB__: true, // add this to easily find tab components from tabs
}
</script>

View File

@@ -0,0 +1,53 @@
<template>
<li>
<div :class="tabClasses" @click="tryActivateTab">
{{ title }}
</div>
</li>
</template>
<script lang="ts" setup>
import { inject, toRefs } from 'vue'
import { TAB_ACTIVATE_INJECTION_KEY, TAB_STYLE_INJECTION_KEY } from '../../config'
import type { TabsVariant } from '../../Tabs.vue'
import { useTabClasses } from './useTabClasses'
const props = defineProps({
name: {
type: String,
required: true,
},
title: {
type: String,
default: '',
},
disabled: {
type: Boolean,
default: false,
},
active: {
type: Boolean,
default: false,
},
})
const variant = inject<TabsVariant>(TAB_STYLE_INJECTION_KEY)
if(!variant) {
console.warn('you can\'t use Tab outside of Tabs component. No tab style injection found')
}
const onActivate = inject<(value: string) => void>(TAB_ACTIVATE_INJECTION_KEY)
if(!onActivate) {
console.warn('you can\'t use Tab outside of Tabs component. No tab activate injection found')
}
const tryActivateTab = () => {
if(props.disabled) return
if(!onActivate) return console.warn('no onActivate')
onActivate(props.name)
}
const { tabClasses } = useTabClasses({
...toRefs(props),
variant,
})
</script>

View File

@@ -0,0 +1,48 @@
import type { Ref } from 'vue'
import { computed } from 'vue'
import type { TabsVariant } from '../../Tabs.vue'
export type TabClassMap = { disabled: string, default: string, active: string }
export type UseTabClassesProps = {
variant?: TabsVariant
active: Ref<boolean>
disabled: Ref<boolean>
}
const defaultTabClasses: TabClassMap = {
default: 'cursor-pointer inline-block p-4 rounded-t-lg hover:text-gray-600 hover:bg-gray-50 dark:hover:bg-gray-800 dark:hover:text-gray-300',
active: 'cursor-pointer inline-block p-4 text-blue-600 bg-gray-100 rounded-t-lg active dark:bg-gray-800 dark:text-blue-500',
disabled: 'inline-block p-4 text-gray-400 rounded-t-lg cursor-not-allowed dark:text-gray-500',
}
const underlineTabClasses: TabClassMap = {
default: 'cursor-pointer inline-block p-4 rounded-t-lg border-b-2 border-transparent hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300',
active: 'cursor-pointer inline-block p-4 text-blue-600 rounded-t-lg border-b-2 border-blue-600 active dark:text-blue-500 dark:border-blue-500',
disabled: 'inline-block p-4 text-gray-400 rounded-t-lg cursor-not-allowed dark:text-gray-500',
}
const pillsTabClasses: TabClassMap = {
default: 'cursor-pointer inline-block py-3 px-4 rounded-lg hover:text-gray-900 hover:bg-gray-100 dark:hover:bg-gray-800 dark:hover:text-white',
active: 'cursor-pointer inline-block py-3 px-4 text-white bg-blue-600 rounded-lg active',
disabled: 'inline-block py-3 px-4 text-gray-400 cursor-not-allowed dark:text-gray-500',
}
export function useTabClasses(props: UseTabClassesProps): {
tabClasses: Ref<string>,
} {
const tabClasses = computed(() => {
const tabClassType: keyof TabClassMap = props.active.value ? 'active' : props.disabled.value ? 'disabled' : 'default'
if(props.variant === 'default')
return defaultTabClasses[tabClassType]
else if(props.variant === 'underline')
return underlineTabClasses[tabClassType]
else if (props.variant === 'pills')
return pillsTabClasses[tabClassType]
return ''
})
return {
tabClasses,
}
}

View File

@@ -0,0 +1,4 @@
export const TAB_STYLE_INJECTION_KEY = 'flowbite-tab-style-injection'
export const TAB_ACTIVE_NAME_INJECTION_KEY = 'flowbite-tab-active-name-injection'
export const TAB_VISIBILITY_DIRECTIVE_INJECTION_KEY = 'flowbite-tab-visibility-directive-injection'
export const TAB_ACTIVATE_INJECTION_KEY = 'flowbite-tab-activate-func-injection'

View File

@@ -0,0 +1,34 @@
import type { Ref } from 'vue'
import { computed } from 'vue'
import type { TabsVariant } from './Tabs.vue'
export type UseTabsClassesProps = {
variant: TabsVariant
}
export function useTabsClasses(props: UseTabsClassesProps): {
ulClasses: Ref<string>,
divClasses: Ref<string>,
} {
const ulClasses = computed(() => {
if(props.variant === 'default')
return 'flex flex-wrap text-sm font-medium text-center text-gray-500 border-b border-gray-200 dark:border-gray-700 dark:text-gray-400'
else if(props.variant === 'pills')
return 'flex flex-wrap text-sm font-medium text-center text-gray-500 dark:text-gray-400'
else if(props.variant === 'underline')
return 'flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400'
return ''
})
const divClasses = computed(() => {
if(props.variant === 'underline')
return 'text-sm font-medium text-center text-gray-500 border-b border-gray-200 dark:text-gray-400 dark:border-gray-700'
return ''
})
return {
ulClasses,
divClasses,
}
}

View File

@@ -1,3 +1,6 @@
export { default as Button } from './components/Button/Button.vue' export { default as Button } from './components/Button/Button.vue'
export { default as Spinner } from './components/Spinner/Spinner.vue' export { default as Spinner } from './components/Spinner/Spinner.vue'
export { default as ButtonGroup } from './components/ButtonGroup/ButtonGroup.vue' export { default as ButtonGroup } from './components/ButtonGroup/ButtonGroup.vue'
export { default as Alert } from './components/Alert/Alert.vue'
export { default as Tabs } from './components/Tabs/Tabs.vue'
export { default as Tab } from './components/Tabs/components/Tab/Tab.vue'

33
src/utils/flatten.ts Normal file
View File

@@ -0,0 +1,33 @@
import { Fragment, createTextVNode, Comment } from 'vue'
import type { VNodeChild, VNode } from 'vue'
// o(n) flatten
export function flatten (
vNodes: VNodeChild[],
filterCommentNode = true,
result: VNode[] = [],
): VNode[] {
vNodes.forEach((vNode) => {
if (vNode === null) return
if (typeof vNode !== 'object') {
if (typeof vNode === 'string' || typeof vNode === 'number') {
result.push(createTextVNode(String(vNode)))
}
return
}
if (Array.isArray(vNode)) {
flatten(vNode, filterCommentNode, result)
return
}
if (vNode.type === Fragment) {
if (vNode.children === null) return
if (Array.isArray(vNode.children)) {
flatten(vNode.children, filterCommentNode, result)
}
// rawSlot
} else if (vNode.type !== Comment) {
result.push(vNode)
}
})
return result
}