feat(ui-components): more new components (#1613)
This commit is contained in:
committed by
GitHub
parent
2dd79d52a7
commit
cf65cfd57b
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<LayoutPanel fancy-glow class="max-w-lg mx-auto w-full">
|
||||
<LayoutPanel fancy-glow no-shadow class="max-w-lg mx-auto w-full">
|
||||
<div class="space-y-4">
|
||||
<div class="flex flex-col items-center space-y-2">
|
||||
<h1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<LayoutPanel fancy-glow class="max-w-lg mx-auto w-full">
|
||||
<LayoutPanel fancy-glow no-shadow class="max-w-lg mx-auto w-full">
|
||||
<div class="space-y-4">
|
||||
<div class="flex flex-col items-center space-y-2">
|
||||
<h1
|
||||
|
||||
@@ -1,14 +1 @@
|
||||
export type InfiniteLoaderState = {
|
||||
/**
|
||||
* Informs the component that this loading has been successful
|
||||
*/
|
||||
loaded: () => void
|
||||
/**
|
||||
* Informs the component that all of the data has been loaded successfully
|
||||
*/
|
||||
complete: () => void
|
||||
/**
|
||||
* Inform the component that this loading failed, the content of the `error` slot will be displayed
|
||||
*/
|
||||
error: () => void
|
||||
}
|
||||
export type { InfiniteLoaderState } from '@speckle/ui-components'
|
||||
|
||||
@@ -64,7 +64,6 @@
|
||||
"nanoid": "^3.0.0",
|
||||
"portal-vue": "^3.0.0",
|
||||
"subscriptions-transport-ws": "^0.11.0",
|
||||
"v3-infinite-loading": "^1.2.2",
|
||||
"vee-validate": "^4.7.0",
|
||||
"vue-advanced-cropper": "^2.8.8",
|
||||
"vue-tippy": "^6.0.0",
|
||||
|
||||
@@ -31,7 +31,7 @@ export const lightThemeVariables = {
|
||||
/* focused primary color */
|
||||
'--primary-focus': '#2563eb',
|
||||
/* muted primary color */
|
||||
'--primary-muted': '#3b82f60d',
|
||||
'--primary-muted': '#e8eff8',
|
||||
|
||||
/* outline variations */
|
||||
'--outline-1': '#3b82f6',
|
||||
@@ -89,7 +89,7 @@ export const darkThemeVariables = {
|
||||
/* focused primary color */
|
||||
'--primary-focus': '#60a5fa',
|
||||
/* muted primary color */
|
||||
'--primary-muted': '#71717a0d',
|
||||
'--primary-muted': '#1d1d20',
|
||||
|
||||
/* outline variations */
|
||||
'--outline-1': '#a1a1aa',
|
||||
|
||||
@@ -6,3 +6,35 @@ Nuxt v3 module that sets up @speckle/ui-components auto-importing like any other
|
||||
|
||||
1. Make sure you've got `@speckle/ui-components` installed and set up
|
||||
1. Install `@speckle/ui-components-nuxt` and add it to your nuxt modules in `nuxt.config.ts`
|
||||
|
||||
1. Add the following to your `build.transpile` array in your nuxt config:
|
||||
|
||||
```js
|
||||
// nuxt.config.js
|
||||
export default {
|
||||
build: {
|
||||
transpile: [
|
||||
'@headlessui/vue',
|
||||
/^@heroicons\/vue/,
|
||||
'@vueuse/core',
|
||||
'@vueuse/shared',
|
||||
'@speckle/ui-components'
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. Add the following to your `vite.resolve.dedupe` array in your nuxt config:
|
||||
|
||||
```js
|
||||
// nuxt.config.js
|
||||
export default {
|
||||
vite: {
|
||||
resolve: {
|
||||
dedupe: ['vee-validate']
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This will ensure that some dependencies are transpiled properly so that they work correctly both during SSR & CSR.
|
||||
|
||||
@@ -13,25 +13,6 @@ Speckle UI component library built with Vue 3 and relying on the Speckle Tailwin
|
||||
|
||||
It's suggested that you also install the `@speckle/ui-components-nuxt` Nuxt module. It will ensure that all of the Vue components can be auto-imported like components in nuxt's `./components` directory. No need to import them manually anymore and you'll also get proper TS typing in your Vue templates out of the box!
|
||||
|
||||
Additionally you should add the following to your `build.transpile` array in your nuxt config:
|
||||
|
||||
```js
|
||||
// nuxt.config.js
|
||||
export default {
|
||||
build: {
|
||||
transpile: [
|
||||
'@headlessui/vue',
|
||||
/^@heroicons\/vue/,
|
||||
'@vueuse/core',
|
||||
'@vueuse/shared',
|
||||
'@speckle/ui-components'
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This will ensure that some dependencies are transpiled properly so that they work correctly both during SSR & CSR.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
#### Form validation doesn't work
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"lodash": "^4.0.0",
|
||||
"lodash-es": "^4.0.0",
|
||||
"nanoid": "^3.0.0",
|
||||
"v3-infinite-loading": "^1.2.2",
|
||||
"vee-validate": "^4.7.0",
|
||||
"vue-tippy": "^6.0.0"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { wait } from '@speckle/shared'
|
||||
import { Meta, StoryObj } from '@storybook/vue3'
|
||||
import { computed, ref } from 'vue'
|
||||
import InfiniteLoading from '~~/src/components/InfiniteLoading.vue'
|
||||
import { InfiniteLoaderState } from '~~/src/helpers/global/components'
|
||||
|
||||
export default {
|
||||
component: InfiniteLoading,
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: 'Infinite loader built on top of v3-infinite-loading'
|
||||
}
|
||||
}
|
||||
}
|
||||
} as Meta
|
||||
|
||||
type FakePaginationItem = {
|
||||
id: string
|
||||
title: string
|
||||
}
|
||||
|
||||
const buildStory = (
|
||||
params?: Partial<{ throwError: boolean; allowRetry: boolean }>
|
||||
): StoryObj => ({
|
||||
render: (args) => ({
|
||||
components: { InfiniteLoading },
|
||||
setup() {
|
||||
const itemsLimit = ref(5)
|
||||
const items = ref([] as FakePaginationItem[])
|
||||
const moreToLoad = computed(() => items.value.length < itemsLimit.value)
|
||||
|
||||
const loadMore = async () => {
|
||||
await wait(1000)
|
||||
|
||||
if (params?.throwError) {
|
||||
throw new Error('Simulated loading failure')
|
||||
}
|
||||
|
||||
const newNumber = items.value.length + 1
|
||||
items.value.push({
|
||||
id: `id-${newNumber}`,
|
||||
title: `Item #${newNumber}`
|
||||
})
|
||||
}
|
||||
|
||||
const infiniteLoad = async (state: InfiniteLoaderState) => {
|
||||
if (!moreToLoad.value) return state.complete()
|
||||
|
||||
try {
|
||||
await loadMore()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
state.error()
|
||||
return
|
||||
}
|
||||
|
||||
state.loaded()
|
||||
if (!moreToLoad.value) {
|
||||
state.complete()
|
||||
}
|
||||
}
|
||||
|
||||
return { args, infiniteLoad, items, moreToLoad, itemsLimit }
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<div v-for="item in items" :key="item.id">
|
||||
{{ item }}
|
||||
</div>
|
||||
<InfiniteLoading @infinite="infiniteLoad" v-bind="args"/>
|
||||
</div>
|
||||
`
|
||||
}),
|
||||
args: {
|
||||
allowRetry: params?.allowRetry || false,
|
||||
settings: {}
|
||||
}
|
||||
})
|
||||
|
||||
export const Default: StoryObj = buildStory()
|
||||
|
||||
export const WithError: StoryObj = buildStory({ throwError: true })
|
||||
|
||||
export const WithRetry: StoryObj = buildStory({ throwError: true, allowRetry: true })
|
||||
+9
-2
@@ -31,8 +31,12 @@
|
||||
<script setup lang="ts">
|
||||
import InternalInfiniteLoading from 'v3-infinite-loading'
|
||||
import { ExclamationTriangleIcon, CheckIcon } from '@heroicons/vue/24/outline'
|
||||
import { InfiniteLoaderState } from '~~/lib/global/helpers/components'
|
||||
import { InfiniteLoaderState } from '~~/src/helpers/global/components'
|
||||
import { Nullable } from '@speckle/shared'
|
||||
import CommonLoadingBar from '~~/src/components/common/loading/Bar.vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { isClient } from '@vueuse/core'
|
||||
import FormButton from '~~/src/components/form/Button.vue'
|
||||
|
||||
defineEmits<{
|
||||
(e: 'infinite', $state: InfiniteLoaderState): void
|
||||
@@ -50,6 +54,9 @@ defineProps<{
|
||||
identifier?: any
|
||||
firstload?: boolean
|
||||
}
|
||||
/**
|
||||
* Whether to allow retry and show a retry button when loading fails
|
||||
*/
|
||||
allowRetry?: boolean
|
||||
}>()
|
||||
|
||||
@@ -57,7 +64,7 @@ const wrapper = ref(null as Nullable<HTMLElement>)
|
||||
const initializeLoader = ref(false)
|
||||
|
||||
// This hack is necessary cause sometimes v3-infinite-loading initializes too early and doesnt trigger
|
||||
if (process.client) {
|
||||
if (isClient) {
|
||||
onMounted(() => {
|
||||
const int = setInterval(() => {
|
||||
if (wrapper.value?.isConnected) {
|
||||
@@ -0,0 +1,114 @@
|
||||
import { Meta, StoryObj } from '@storybook/vue3'
|
||||
import CommonAlert from '~~/src/components/common/Alert.vue'
|
||||
|
||||
export default {
|
||||
component: CommonAlert,
|
||||
argTypes: {
|
||||
color: {
|
||||
options: ['success', 'danger', 'warning', 'info'],
|
||||
control: { type: 'select' }
|
||||
}
|
||||
}
|
||||
} as Meta
|
||||
|
||||
export const Default: StoryObj = {
|
||||
render: (args) => ({
|
||||
components: { CommonAlert },
|
||||
setup() {
|
||||
return { args }
|
||||
},
|
||||
template: `
|
||||
<CommonAlert v-bind="args">
|
||||
<template #title>
|
||||
Some title
|
||||
</template>
|
||||
<template #description>
|
||||
Some description
|
||||
</template>
|
||||
</CommonAlert>
|
||||
`
|
||||
}),
|
||||
args: {
|
||||
color: 'success',
|
||||
withDismiss: false,
|
||||
actions: [
|
||||
{ title: 'View', url: 'https://google.com' },
|
||||
{ title: 'Open', onClick: () => console.log('click') }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export const WithDismisser = {
|
||||
...Default,
|
||||
args: {
|
||||
...Default.args,
|
||||
withDismiss: true
|
||||
}
|
||||
}
|
||||
|
||||
export const WithoutDescription: StoryObj = {
|
||||
render: (args) => ({
|
||||
components: { CommonAlert },
|
||||
setup() {
|
||||
return { args }
|
||||
},
|
||||
template: `
|
||||
<CommonAlert v-bind="args">
|
||||
<template #title>
|
||||
Some title
|
||||
</template>
|
||||
</CommonAlert>
|
||||
`
|
||||
}),
|
||||
args: {
|
||||
...Default.args
|
||||
}
|
||||
}
|
||||
|
||||
export const WithoutDescriptionAndWithDismisser = {
|
||||
...WithoutDescription,
|
||||
args: {
|
||||
...WithoutDescription.args,
|
||||
withDismiss: true
|
||||
}
|
||||
}
|
||||
|
||||
export const WithoutActions = {
|
||||
...Default,
|
||||
args: {
|
||||
...Default.args,
|
||||
actions: undefined
|
||||
}
|
||||
}
|
||||
|
||||
export const WithoutDescriptionAndActions = {
|
||||
...WithoutDescription,
|
||||
args: {
|
||||
...WithoutDescription.args,
|
||||
actions: undefined
|
||||
}
|
||||
}
|
||||
|
||||
export const Info = {
|
||||
...Default,
|
||||
args: {
|
||||
...Default.args,
|
||||
color: 'info'
|
||||
}
|
||||
}
|
||||
|
||||
export const Danger = {
|
||||
...Default,
|
||||
args: {
|
||||
...Default.args,
|
||||
color: 'danger'
|
||||
}
|
||||
}
|
||||
|
||||
export const Warning = {
|
||||
...Default,
|
||||
args: {
|
||||
...Default.args,
|
||||
color: 'warning'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<div class="rounded-md" :class="[containerClasses, textClasses]">
|
||||
<div class="flex" :class="[hasDescription ? '' : 'items-center space-x-2']">
|
||||
<div class="flex-shrink-0">
|
||||
<CheckCircleIcon class="h-5 w-5" :class="iconClasses" aria-hidden="true" />
|
||||
</div>
|
||||
<div
|
||||
class="ml-3 grow"
|
||||
:class="[hasDescription ? '' : 'flex items-center space-x-2']"
|
||||
>
|
||||
<h3 class="text-sm" :class="[hasDescription ? 'font-medium' : '']">
|
||||
<slot name="title">Title</slot>
|
||||
</h3>
|
||||
<div v-if="hasDescription" class="mt-2 text-sm">
|
||||
<slot name="description">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquid pariatur,
|
||||
ipsum similique veniam.
|
||||
</slot>
|
||||
</div>
|
||||
<div :class="[hasDescription ? (actions?.length ? 'mt-4' : '') : 'grow flex']">
|
||||
<div
|
||||
class="flex"
|
||||
:class="['space-x-2', hasDescription ? '' : 'grow justify-end']"
|
||||
>
|
||||
<FormButton
|
||||
v-for="(action, i) in actions || []"
|
||||
:key="i"
|
||||
:color="color"
|
||||
size="sm"
|
||||
:to="action.url"
|
||||
:external="action.externalUrl || false"
|
||||
@click="action.onClick || noop"
|
||||
>
|
||||
{{ action.title }}
|
||||
</FormButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="withDismiss"
|
||||
class="flex"
|
||||
:class="[hasDescription ? 'items-start' : 'items-center']"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex rounded-md focus:outline-none focus:ring-2"
|
||||
:class="buttonClasses"
|
||||
@click="$emit('dismiss')"
|
||||
>
|
||||
<span class="sr-only">Dismiss</span>
|
||||
<XMarkIcon class="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { CheckCircleIcon, XMarkIcon } from '@heroicons/vue/20/solid'
|
||||
import { noop } from 'lodash'
|
||||
import { computed, useSlots } from 'vue'
|
||||
import FormButton from '~~/src/components/form/Button.vue'
|
||||
|
||||
type AlertColor = 'success' | 'danger' | 'warning' | 'info'
|
||||
|
||||
defineEmits<{ (e: 'dismiss'): void }>()
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
color?: AlertColor
|
||||
withDismiss?: boolean
|
||||
actions?: Array<{
|
||||
title: string
|
||||
url?: string
|
||||
onClick?: () => void
|
||||
externalUrl?: boolean
|
||||
}>
|
||||
}>(),
|
||||
{
|
||||
color: 'success'
|
||||
}
|
||||
)
|
||||
|
||||
const slots = useSlots()
|
||||
const hasDescription = computed(() => !!slots['description'])
|
||||
|
||||
const containerClasses = computed(() => {
|
||||
const classParts: string[] = []
|
||||
|
||||
classParts.push(hasDescription.value ? 'p-4' : 'p-2')
|
||||
|
||||
switch (props.color) {
|
||||
case 'success':
|
||||
classParts.push('bg-success-lighter border-l-4 border-success')
|
||||
break
|
||||
case 'info':
|
||||
classParts.push('bg-info-lighter border-l-4 border-info')
|
||||
break
|
||||
case 'danger':
|
||||
classParts.push('bg-danger-lighter border-l-4 border-danger')
|
||||
break
|
||||
case 'warning':
|
||||
classParts.push('bg-warning-lighter border-l-4 border-warning')
|
||||
break
|
||||
}
|
||||
|
||||
return classParts.join(' ')
|
||||
})
|
||||
|
||||
const textClasses = computed(() => {
|
||||
const classParts: string[] = []
|
||||
|
||||
switch (props.color) {
|
||||
case 'success':
|
||||
classParts.push('text-success-darker')
|
||||
break
|
||||
case 'info':
|
||||
classParts.push('text-info-darker')
|
||||
break
|
||||
case 'danger':
|
||||
classParts.push('text-danger-darker')
|
||||
break
|
||||
case 'warning':
|
||||
classParts.push('text-warning-darker')
|
||||
break
|
||||
}
|
||||
|
||||
return classParts.join(' ')
|
||||
})
|
||||
|
||||
const iconClasses = computed(() => {
|
||||
const classParts: string[] = []
|
||||
|
||||
switch (props.color) {
|
||||
case 'success':
|
||||
classParts.push('text-success')
|
||||
break
|
||||
case 'info':
|
||||
classParts.push('text-info')
|
||||
break
|
||||
case 'danger':
|
||||
classParts.push('text-danger')
|
||||
break
|
||||
case 'warning':
|
||||
classParts.push('text-warning')
|
||||
break
|
||||
}
|
||||
|
||||
return classParts.join(' ')
|
||||
})
|
||||
|
||||
const buttonClasses = computed(() => {
|
||||
const classParts: string[] = []
|
||||
|
||||
switch (props.color) {
|
||||
case 'success':
|
||||
classParts.push('bg-success-lighter ring-success')
|
||||
break
|
||||
case 'info':
|
||||
classParts.push('bg-info-lighter ring-info')
|
||||
break
|
||||
case 'danger':
|
||||
classParts.push('bg-danger-lighter ring-danger')
|
||||
break
|
||||
case 'warning':
|
||||
classParts.push('bg-warning-lighter ring-warning')
|
||||
break
|
||||
}
|
||||
|
||||
return classParts.join(' ')
|
||||
})
|
||||
</script>
|
||||
@@ -33,6 +33,10 @@ export default {
|
||||
options: ['horizontal', 'vertical'],
|
||||
control: { type: 'select' }
|
||||
},
|
||||
stepsPadding: {
|
||||
options: ['base', 'xs', 'sm'],
|
||||
control: { type: 'select' }
|
||||
},
|
||||
'update:modelValue': {
|
||||
type: 'function',
|
||||
action: 'v-model'
|
||||
@@ -65,7 +69,8 @@ export const Default: StoryType = {
|
||||
orientation: 'horizontal',
|
||||
steps: testSteps,
|
||||
modelValue: 1,
|
||||
nonInteractive: false
|
||||
nonInteractive: false,
|
||||
stepsPadding: 'base'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +86,13 @@ export const VersionBasic: StoryType = mergeStories(Default, {
|
||||
}
|
||||
})
|
||||
|
||||
export const BasicVertical: StoryType = mergeStories(Default, {
|
||||
args: {
|
||||
basic: true,
|
||||
orientation: 'vertical'
|
||||
}
|
||||
})
|
||||
|
||||
export const StartOnNegativeStep: StoryType = mergeStories(Default, {
|
||||
args: {
|
||||
modelValue: -1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<nav class="flex justify-center" :aria-label="ariaLabel || 'Progress steps'">
|
||||
<ol :class="[listClasses, basic ? 'basic' : '']">
|
||||
<ol :class="[listClasses, extraListClasses]">
|
||||
<li v-for="(step, i) in steps" :key="step.name">
|
||||
<a
|
||||
v-if="isFinishedStep(i)"
|
||||
@@ -67,7 +67,7 @@
|
||||
<script setup lang="ts">
|
||||
import { CheckCircleIcon } from '@heroicons/vue/20/solid'
|
||||
import { computed, toRefs } from 'vue'
|
||||
import { useStepsInternals } from '~~/src/composables/common/steps'
|
||||
import { StepsPadding, useStepsInternals } from '~~/src/composables/common/steps'
|
||||
import { BulletStepType, HorizontalOrVertical } from '~~/src/helpers/common/components'
|
||||
import { TailwindBreakpoints } from '~~/src/helpers/tailwind'
|
||||
|
||||
@@ -83,6 +83,7 @@ const props = defineProps<{
|
||||
modelValue?: number
|
||||
goVerticalBelow?: TailwindBreakpoints
|
||||
nonInteractive?: boolean
|
||||
stepsPadding?: StepsPadding
|
||||
}>()
|
||||
|
||||
const { isCurrentStep, isFinishedStep, switchStep, listClasses, linkClasses } =
|
||||
@@ -92,7 +93,18 @@ const { isCurrentStep, isFinishedStep, switchStep, listClasses, linkClasses } =
|
||||
})
|
||||
|
||||
const labelClasses = computed(() => {
|
||||
const classParts: string[] = ['ml-3 h6 font-medium leading-7']
|
||||
const classParts: string[] = ['h6 font-medium leading-7']
|
||||
|
||||
let leftMargin: string
|
||||
if (props.stepsPadding === 'xs') {
|
||||
leftMargin = 'ml-1'
|
||||
} else if (props.stepsPadding === 'sm') {
|
||||
leftMargin = 'ml-2'
|
||||
} else {
|
||||
leftMargin = 'ml-3'
|
||||
}
|
||||
|
||||
classParts.push(leftMargin)
|
||||
|
||||
if (props.basic) {
|
||||
classParts.push('sr-only')
|
||||
@@ -100,9 +112,14 @@ const labelClasses = computed(() => {
|
||||
|
||||
return classParts.join(' ')
|
||||
})
|
||||
|
||||
const extraListClasses = computed(() => {
|
||||
const classParts: string[] = []
|
||||
|
||||
if (props.basic) {
|
||||
classParts.push('basic')
|
||||
}
|
||||
|
||||
return classParts.join(' ')
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
.basic {
|
||||
@apply space-x-4 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -54,6 +54,10 @@ export default {
|
||||
'update:modelValue': {
|
||||
type: 'function',
|
||||
action: 'v-model'
|
||||
},
|
||||
stepsPadding: {
|
||||
options: ['base', 'xs', 'sm'],
|
||||
control: { type: 'select' }
|
||||
}
|
||||
},
|
||||
parameters: {
|
||||
@@ -81,7 +85,8 @@ export const Default: StoryType = {
|
||||
ariaLabel: 'Steps ARIA title!',
|
||||
orientation: 'horizontal',
|
||||
steps: testStepsWithDescription,
|
||||
modelValue: 1
|
||||
modelValue: 1,
|
||||
stepsPadding: 'base'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
<script setup lang="ts">
|
||||
import { CheckIcon } from '@heroicons/vue/20/solid'
|
||||
import { toRefs } from 'vue'
|
||||
import { useStepsInternals } from '~~/src/composables/common/steps'
|
||||
import { StepsPadding, useStepsInternals } from '~~/src/composables/common/steps'
|
||||
import { HorizontalOrVertical, NumberStepType } from '~~/src/helpers/common/components'
|
||||
import { TailwindBreakpoints } from '~~/src/helpers/tailwind'
|
||||
|
||||
@@ -91,6 +91,7 @@ const props = defineProps<{
|
||||
modelValue?: number
|
||||
goVerticalBelow?: TailwindBreakpoints
|
||||
nonInteractive?: boolean
|
||||
stepsPadding?: StepsPadding
|
||||
}>()
|
||||
|
||||
const {
|
||||
|
||||
@@ -9,7 +9,7 @@ export default {
|
||||
component: FormButton,
|
||||
argTypes: {
|
||||
color: {
|
||||
options: ['default', 'invert', 'danger', 'warning', 'secondary'],
|
||||
options: ['default', 'invert', 'danger', 'warning', 'secondary', 'info'],
|
||||
control: { type: 'select' }
|
||||
},
|
||||
outlined: {
|
||||
@@ -123,6 +123,42 @@ export const WarningButton: StoryObj = mergeStories(Default, {
|
||||
}
|
||||
})
|
||||
|
||||
export const InfoButton: StoryObj = mergeStories(Default, {
|
||||
args: {
|
||||
color: 'info'
|
||||
}
|
||||
})
|
||||
|
||||
export const DangerButton: StoryObj = mergeStories(Default, {
|
||||
args: {
|
||||
color: 'danger'
|
||||
}
|
||||
})
|
||||
|
||||
export const SuccessButton: StoryObj = mergeStories(Default, {
|
||||
args: {
|
||||
color: 'success'
|
||||
}
|
||||
})
|
||||
|
||||
export const SecondaryButton: StoryObj = mergeStories(Default, {
|
||||
args: {
|
||||
color: 'secondary'
|
||||
}
|
||||
})
|
||||
|
||||
export const InvertButton: StoryObj = mergeStories(Default, {
|
||||
args: {
|
||||
color: 'invert'
|
||||
}
|
||||
})
|
||||
|
||||
export const CardButton: StoryObj = mergeStories(Default, {
|
||||
args: {
|
||||
color: 'card'
|
||||
}
|
||||
})
|
||||
|
||||
export const RoundedOutlined: StoryObj = mergeStories(Default, {
|
||||
args: {
|
||||
rounded: true,
|
||||
|
||||
@@ -42,6 +42,7 @@ type FormButtonColor =
|
||||
| 'success'
|
||||
| 'card'
|
||||
| 'secondary'
|
||||
| 'info'
|
||||
|
||||
const emit = defineEmits<{
|
||||
/**
|
||||
@@ -232,6 +233,9 @@ const bgAndBorderClasses = computed(() => {
|
||||
case 'warning':
|
||||
classParts.push(props.outlined ? 'border-warning' : 'bg-warning border-warning')
|
||||
break
|
||||
case 'info':
|
||||
classParts.push(props.outlined ? 'border-info' : 'bg-info border-info')
|
||||
break
|
||||
case 'success':
|
||||
classParts.push(props.outlined ? 'border-success' : 'bg-success border-success')
|
||||
break
|
||||
@@ -276,6 +280,11 @@ const foregroundClasses = computed(() => {
|
||||
props.outlined ? 'text-warning' : 'text-foundation dark:text-foreground'
|
||||
)
|
||||
break
|
||||
case 'info':
|
||||
classParts.push(
|
||||
props.outlined ? 'text-info' : 'text-foundation dark:text-foreground'
|
||||
)
|
||||
break
|
||||
case 'success':
|
||||
classParts.push(
|
||||
props.outlined ? 'text-success' : 'text-foundation dark:text-foreground'
|
||||
@@ -312,6 +321,8 @@ const foregroundClasses = computed(() => {
|
||||
classParts.push('text-success')
|
||||
} else if (props.color === 'warning') {
|
||||
classParts.push('text-warning')
|
||||
} else if (props.color === 'info') {
|
||||
classParts.push('text-info')
|
||||
} else if (props.color === 'danger') {
|
||||
classParts.push('text-danger')
|
||||
} else {
|
||||
@@ -341,6 +352,9 @@ const ringClasses = computed(() => {
|
||||
case 'warning':
|
||||
classParts.push('hover:ring-4 ring-warning-lighter dark:ring-warning-darker')
|
||||
break
|
||||
case 'info':
|
||||
classParts.push('hover:ring-4 ring-info-lighter dark:ring-info-darker')
|
||||
break
|
||||
case 'success':
|
||||
classParts.push('hover:ring-4 ring-success-lighter dark:ring-success-darker')
|
||||
break
|
||||
|
||||
@@ -86,7 +86,7 @@ import { ConcreteComponent, PropType, computed, ref, toRefs, useSlots } from 'vu
|
||||
import { Nullable, Optional } from '@speckle/shared'
|
||||
import { useTextInputCore } from '~~/src/composables/form/textInput'
|
||||
|
||||
type InputType = 'text' | 'email' | 'password' | 'url' | 'search'
|
||||
type InputType = 'text' | 'email' | 'password' | 'url' | 'search' | 'number' | string
|
||||
type InputSize = 'sm' | 'base' | 'lg' | 'xl'
|
||||
type InputColor = 'page' | 'foundation'
|
||||
|
||||
|
||||
@@ -125,6 +125,31 @@ export const Default: StoryType = {
|
||||
}
|
||||
}
|
||||
|
||||
export const WithLabel: StoryType = {
|
||||
...Default,
|
||||
args: {
|
||||
...Default.args,
|
||||
showLabel: true
|
||||
}
|
||||
}
|
||||
|
||||
export const WithLabelAndHelp: StoryType = {
|
||||
...Default,
|
||||
args: {
|
||||
...Default.args,
|
||||
showLabel: true,
|
||||
help: 'Some help text'
|
||||
}
|
||||
}
|
||||
|
||||
export const Tinted: StoryType = {
|
||||
...Default,
|
||||
args: {
|
||||
...Default.args,
|
||||
buttonStyle: 'tinted'
|
||||
}
|
||||
}
|
||||
|
||||
export const LimitedWidth: StoryType = {
|
||||
...Default,
|
||||
render: (args, ctx) => ({
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
as="div"
|
||||
>
|
||||
<ListboxLabel
|
||||
class="block label text-foreground"
|
||||
class="block label text-foreground-2 mb-2"
|
||||
:class="{ 'sr-only': !showLabel }"
|
||||
>
|
||||
{{ label }}
|
||||
@@ -138,12 +138,7 @@
|
||||
</Transition>
|
||||
</div>
|
||||
</Listbox>
|
||||
<p
|
||||
v-if="helpTipId"
|
||||
:id="helpTipId"
|
||||
class="mt-2 ml-3 text-sm"
|
||||
:class="helpTipClasses"
|
||||
>
|
||||
<p v-if="helpTipId" :id="helpTipId" class="mt-2 text-sm" :class="helpTipClasses">
|
||||
{{ helpTip }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -180,7 +175,7 @@ import CommonLoadingBar from '~~/src/components/common/loading/Bar.vue'
|
||||
// @ts-ignore
|
||||
import { directive as vTippy } from 'vue-tippy'
|
||||
|
||||
type ButtonStyle = 'base' | 'simple'
|
||||
type ButtonStyle = 'base' | 'simple' | 'tinted'
|
||||
type SingleItem = any
|
||||
type ValueType = SingleItem | SingleItem[] | undefined
|
||||
|
||||
@@ -284,7 +279,7 @@ const props = defineProps({
|
||||
* Validation stuff
|
||||
*/
|
||||
rules: {
|
||||
type: [String, Object, Function, Array] as PropType<RuleExpression<string>>,
|
||||
type: [String, Object, Function, Array] as PropType<RuleExpression<ValueType>>,
|
||||
default: undefined
|
||||
},
|
||||
/**
|
||||
@@ -354,7 +349,7 @@ const renderClearButton = computed(
|
||||
)
|
||||
|
||||
const buttonsWrapperClasses = computed(() => {
|
||||
const classParts: string[] = ['relative flex group', props.showLabel ? 'mt-1' : '']
|
||||
const classParts: string[] = ['relative flex group']
|
||||
|
||||
if (props.buttonStyle !== 'simple') {
|
||||
classParts.push('hover:shadow rounded-md')
|
||||
@@ -389,13 +384,19 @@ const clearButtonClasses = computed(() => {
|
||||
'relative z-[1]',
|
||||
'flex items-center justify-center text-center shrink-0',
|
||||
'rounded-r-md overflow-hidden transition-all',
|
||||
'text-foreground',
|
||||
hasValueSelected.value ? `w-6 ${commonButtonClasses.value}` : 'w-0'
|
||||
]
|
||||
|
||||
if (!isDisabled.value) {
|
||||
classParts.push(
|
||||
'bg-primary-muted hover:bg-primary hover:text-foreground-on-primary'
|
||||
'hover:bg-primary hover:text-foreground-on-primary dark:text-foreground-on-primary'
|
||||
)
|
||||
if (props.buttonStyle === 'tinted') {
|
||||
classParts.push('bg-outline-3')
|
||||
} else {
|
||||
classParts.push('bg-primary-muted')
|
||||
}
|
||||
}
|
||||
|
||||
return classParts.join(' ')
|
||||
@@ -413,7 +414,11 @@ const buttonClasses = computed(() => {
|
||||
classParts.push('py-2 px-3')
|
||||
|
||||
if (!isDisabled.value) {
|
||||
classParts.push('bg-foundation text-foreground')
|
||||
if (props.buttonStyle === 'tinted') {
|
||||
classParts.push('bg-foundation-page text-foreground')
|
||||
} else {
|
||||
classParts.push('bg-foundation text-foreground')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,7 +497,6 @@ const itemKey = (v: SingleItem): string | number =>
|
||||
props.by ? (v[props.by] as string) : v
|
||||
|
||||
const triggerSearch = async () => {
|
||||
console.log('triggerSearch')
|
||||
if (!isAsyncSearchMode.value || !props.getSearchResults) return
|
||||
|
||||
isAsyncLoading.value = true
|
||||
|
||||
@@ -42,6 +42,15 @@ export const Default: StoryObj = {
|
||||
}),
|
||||
args: {
|
||||
maxWidth: 'sm',
|
||||
hideCloser: false
|
||||
hideCloser: false,
|
||||
preventCloseOnClickOutside: false
|
||||
}
|
||||
}
|
||||
|
||||
export const ManualCloseOnly = {
|
||||
...Default,
|
||||
args: {
|
||||
...Default.args,
|
||||
preventCloseOnClickOutside: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<TransitionRoot as="template" :show="open">
|
||||
<Dialog as="div" class="relative z-40" @close="open = false">
|
||||
<Dialog as="div" class="relative z-40" @close="onClose">
|
||||
<TransitionChild
|
||||
as="template"
|
||||
enter="ease-out duration-300"
|
||||
@@ -66,6 +66,10 @@ const props = defineProps<{
|
||||
open: boolean
|
||||
maxWidth?: MaxWidthValue
|
||||
hideCloser?: boolean
|
||||
/**
|
||||
* Prevent modal from closing when the user clicks outside of the modal or presses Esc
|
||||
*/
|
||||
preventCloseOnClickOutside?: boolean
|
||||
}>()
|
||||
|
||||
const open = computed({
|
||||
@@ -112,4 +116,9 @@ const widthClasses = computed(() => {
|
||||
|
||||
return classParts.join(' ')
|
||||
})
|
||||
|
||||
const onClose = () => {
|
||||
if (props.preventCloseOnClickOutside) return
|
||||
open.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { Meta, StoryObj } from '@storybook/vue3'
|
||||
import LayoutPanel from '~~/src/components/layout/Panel.vue'
|
||||
|
||||
export default {
|
||||
component: LayoutPanel,
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: 'A basic panel that can be used as a basis for various cards/panels'
|
||||
}
|
||||
}
|
||||
}
|
||||
} as Meta
|
||||
|
||||
export const Default: StoryObj = {
|
||||
render: (args) => ({
|
||||
components: { LayoutPanel },
|
||||
setup() {
|
||||
return { args }
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<LayoutPanel v-bind="args">
|
||||
<template #default>
|
||||
Default slot
|
||||
</template>
|
||||
<template #header>
|
||||
Header slot
|
||||
</template>
|
||||
<template #footer>
|
||||
Footer slot
|
||||
</template>
|
||||
</LayoutPanel>
|
||||
</div>
|
||||
`
|
||||
}),
|
||||
args: {
|
||||
form: false,
|
||||
ring: false,
|
||||
customPadding: false,
|
||||
fancyGlow: false,
|
||||
noShadow: false
|
||||
}
|
||||
}
|
||||
|
||||
export const CustomPadding: StoryObj = {
|
||||
render: (args) => ({
|
||||
components: { LayoutPanel },
|
||||
setup() {
|
||||
return { args }
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<LayoutPanel v-bind="args">
|
||||
<template #default>
|
||||
<div class="p-2">
|
||||
Default slot
|
||||
</div>
|
||||
</template>
|
||||
<template #header>
|
||||
Header slot (no padding)
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="p-8">
|
||||
Footer slot (big padding)
|
||||
</div>
|
||||
</template>
|
||||
</LayoutPanel>
|
||||
</div>
|
||||
`
|
||||
}),
|
||||
args: {
|
||||
form: false,
|
||||
ring: false,
|
||||
customPadding: true
|
||||
}
|
||||
}
|
||||
|
||||
export const WithRingOutline = {
|
||||
...Default,
|
||||
args: {
|
||||
...Default.args,
|
||||
ring: true
|
||||
}
|
||||
}
|
||||
|
||||
export const WithFancyGlow = {
|
||||
...Default,
|
||||
args: {
|
||||
...Default.args,
|
||||
fancyGlow: true
|
||||
}
|
||||
}
|
||||
|
||||
export const NoShadow = {
|
||||
...Default,
|
||||
args: {
|
||||
...Default.args,
|
||||
noShadow: true
|
||||
}
|
||||
}
|
||||
+36
-19
@@ -12,51 +12,68 @@
|
||||
]"
|
||||
@submit="emit('submit', $event)"
|
||||
>
|
||||
<div v-if="$slots.header" class="px-4 py-4 sm:px-6">
|
||||
<div v-if="$slots.header" :class="secondarySlotPaddingClasses">
|
||||
<slot name="header" />
|
||||
</div>
|
||||
<div class="grow px-4 py-4 sm:p-6">
|
||||
<div :class="['grow', defaultSlotPaddingClasses]">
|
||||
<slot />
|
||||
</div>
|
||||
<div v-if="$slots.footer" class="px-4 py-4 sm:px-6">
|
||||
<div v-if="$slots.footer" :class="secondarySlotPaddingClasses">
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</Component>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const emit = defineEmits<{ (e: 'submit', val: SubmitEvent): void }>()
|
||||
|
||||
type RoundedBorderSize = '2xl' | 'base'
|
||||
|
||||
const props = defineProps({
|
||||
/**
|
||||
* Use a `<form/>` element as a wrapper that will emit 'submit' events out from the component when they occur
|
||||
*/
|
||||
form: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
roundedBorderSize: {
|
||||
type: String as PropType<RoundedBorderSize>,
|
||||
default: '2xl'
|
||||
/**
|
||||
* Add a ring outline on hover
|
||||
*/
|
||||
ring: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* Add a primary-colored glow on hover
|
||||
*/
|
||||
fancyGlow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
customPadding: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
noShadow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const secondarySlotPaddingClasses = computed(() =>
|
||||
props.customPadding ? '' : 'px-4 py-4 sm:px-6'
|
||||
)
|
||||
const defaultSlotPaddingClasses = computed(() =>
|
||||
props.customPadding ? '' : 'px-4 py-4 sm:p-6'
|
||||
)
|
||||
|
||||
const computedClasses = computed(() => {
|
||||
const classParts: string[] = []
|
||||
if (!props.fancyGlow) classParts.push('shadow')
|
||||
switch (props.roundedBorderSize) {
|
||||
case 'base':
|
||||
classParts.push('rounded-md')
|
||||
break
|
||||
case '2xl':
|
||||
default:
|
||||
classParts.push('rounded-md')
|
||||
break
|
||||
const classParts: string[] = ['rounded-lg']
|
||||
|
||||
if (!props.noShadow) classParts.push('shadow')
|
||||
if (props.ring) {
|
||||
classParts.push('ring-outline-2 hover:ring-2')
|
||||
}
|
||||
|
||||
return classParts.join(' ')
|
||||
@@ -1,7 +1,9 @@
|
||||
import { ToRefs, computed } from 'vue'
|
||||
import { HorizontalOrVertical, StepCoreType } from '~~/src/helpers/common/components'
|
||||
import { clamp } from 'lodash'
|
||||
import { TailwindBreakpoints } from '~~/src/helpers/tailwind'
|
||||
import { TailwindBreakpoints, markClassesUsed } from '~~/src/helpers/tailwind'
|
||||
|
||||
export type StepsPadding = 'base' | 'xs' | 'sm'
|
||||
|
||||
export function useStepsInternals(params: {
|
||||
props: ToRefs<{
|
||||
@@ -10,13 +12,21 @@ export function useStepsInternals(params: {
|
||||
modelValue?: number
|
||||
goVerticalBelow?: TailwindBreakpoints
|
||||
nonInteractive?: boolean
|
||||
stepsPadding?: StepsPadding
|
||||
}>
|
||||
emit: {
|
||||
(e: 'update:modelValue', val: number): void
|
||||
}
|
||||
}) {
|
||||
const {
|
||||
props: { modelValue, steps, orientation, goVerticalBelow, nonInteractive },
|
||||
props: {
|
||||
modelValue,
|
||||
steps,
|
||||
orientation,
|
||||
goVerticalBelow,
|
||||
nonInteractive,
|
||||
stepsPadding
|
||||
},
|
||||
emit
|
||||
} = params
|
||||
|
||||
@@ -51,29 +61,42 @@ export function useStepsInternals(params: {
|
||||
const listClasses = computed(() => {
|
||||
const classParts: string[] = ['flex']
|
||||
|
||||
let paddingHorizontal: string
|
||||
let paddingVertical: string
|
||||
if (stepsPadding?.value === 'xs') {
|
||||
paddingHorizontal = 'space-x-2'
|
||||
paddingVertical = 'space-y-1'
|
||||
} else if (stepsPadding?.value === 'sm') {
|
||||
paddingHorizontal = 'space-x-4'
|
||||
paddingVertical = 'space-y-1'
|
||||
} else {
|
||||
paddingHorizontal = 'space-x-8'
|
||||
paddingVertical = 'space-y-4'
|
||||
}
|
||||
|
||||
classParts.push('flex')
|
||||
if (finalOrientation.value === 'vertical' || goVerticalBelow?.value) {
|
||||
classParts.push('flex-col space-y-4 justify-center')
|
||||
classParts.push(`flex-col ${paddingVertical} justify-center`)
|
||||
|
||||
if (goVerticalBelow?.value === TailwindBreakpoints.sm) {
|
||||
classParts.push(
|
||||
'sm:flex-row sm:space-y-0 sm:justify-start sm:space-x-8 sm:items-center'
|
||||
`sm:flex-row sm:space-y-0 sm:justify-start sm:${paddingHorizontal} sm:items-center`
|
||||
)
|
||||
} else if (goVerticalBelow?.value === TailwindBreakpoints.md) {
|
||||
classParts.push(
|
||||
'md:flex-row md:space-y-0 md:justify-start md:space-x-8 md:items-center'
|
||||
`md:flex-row md:space-y-0 md:justify-start md:${paddingHorizontal} md:items-center`
|
||||
)
|
||||
} else if (goVerticalBelow?.value === TailwindBreakpoints.lg) {
|
||||
classParts.push(
|
||||
'lg:flex-row lg:space-y-0 lg:justify-start lg:space-x-8 lg:items-center'
|
||||
`lg:flex-row lg:space-y-0 lg:justify-start lg:${paddingHorizontal} lg:items-center`
|
||||
)
|
||||
} else if (goVerticalBelow?.value === TailwindBreakpoints.xl) {
|
||||
classParts.push(
|
||||
'xl:flex-row xl:space-y-0 xl:justify-start xl:space-x-8 xl:items-center'
|
||||
`xl:flex-row xl:space-y-0 xl:justify-start xl:${paddingHorizontal} xl:items-center`
|
||||
)
|
||||
}
|
||||
} else {
|
||||
classParts.push('flex-row space-x-8 items-center')
|
||||
classParts.push(`flex-row ${paddingHorizontal} items-center`)
|
||||
}
|
||||
|
||||
return classParts.join(' ')
|
||||
@@ -100,3 +123,19 @@ export function useStepsInternals(params: {
|
||||
orientation: finalOrientation
|
||||
}
|
||||
}
|
||||
|
||||
// to allow for dynamic class building above:
|
||||
markClassesUsed([
|
||||
'sm:space-x-8',
|
||||
'md:space-x-8',
|
||||
'lg:space-x-8',
|
||||
'xl:space-x-8',
|
||||
'sm:space-x-2',
|
||||
'md:space-x-2',
|
||||
'lg:space-x-2',
|
||||
'xl:space-x-2',
|
||||
'sm:space-x-4',
|
||||
'md:space-x-4',
|
||||
'lg:space-x-4',
|
||||
'xl:space-x-4'
|
||||
])
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
export type InfiniteLoaderState = {
|
||||
/**
|
||||
* Informs the component that this loading has been successful
|
||||
*/
|
||||
loaded: () => void
|
||||
/**
|
||||
* Informs the component that all of the data has been loaded successfully
|
||||
*/
|
||||
complete: () => void
|
||||
/**
|
||||
* Inform the component that this loading failed, the content of the `error` slot will be displayed
|
||||
*/
|
||||
error: () => void
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
let junkVariable: string[] = []
|
||||
|
||||
/**
|
||||
* If you use concatenation or variables to build tailwind classes, PurgeCSS won't pick up on them
|
||||
* during build and will not add them to the build. So you can use this function to just add string
|
||||
@@ -7,9 +9,9 @@
|
||||
* variable so it's better to use this instead.
|
||||
*/
|
||||
export function markClassesUsed(classes: string[]) {
|
||||
// this doesn't do anything, we just need PurgeCSS to be able to read
|
||||
// invocations of this function
|
||||
false && classes
|
||||
// this doesn't do anything, except trick the compiler into thinking this isn't a pure
|
||||
// function so that the invocations aren't tree-shaken out
|
||||
junkVariable = junkVariable ? classes : classes.slice()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,6 +43,10 @@ import {
|
||||
import LayoutMenu from '~~/src/components/layout/Menu.vue'
|
||||
import { LayoutMenuItem, LayoutTabItem } from '~~/src/helpers/layout/components'
|
||||
import LayoutTabs from '~~/src/components/layout/Tabs.vue'
|
||||
import InfiniteLoading from '~~/src/components/InfiniteLoading.vue'
|
||||
import { InfiniteLoaderState } from '~~/src/helpers/global/components'
|
||||
import LayoutPanel from '~~/src/components/layout/Panel.vue'
|
||||
import CommonAlert from '~~/src/components/common/Alert.vue'
|
||||
|
||||
export {
|
||||
GlobalToastRenderer,
|
||||
@@ -78,7 +82,10 @@ export {
|
||||
useOnBeforeWindowUnload,
|
||||
useResponsiveHorizontalDirectionCalculation,
|
||||
LayoutMenu,
|
||||
LayoutTabs
|
||||
LayoutTabs,
|
||||
InfiniteLoading,
|
||||
LayoutPanel,
|
||||
CommonAlert
|
||||
}
|
||||
export type {
|
||||
ToastNotification,
|
||||
@@ -86,5 +93,6 @@ export type {
|
||||
NumberStepType,
|
||||
HorizontalOrVertical,
|
||||
LayoutMenuItem,
|
||||
LayoutTabItem
|
||||
LayoutTabItem,
|
||||
InfiniteLoaderState
|
||||
}
|
||||
|
||||
@@ -11216,7 +11216,6 @@ __metadata:
|
||||
tailwindcss: ^3.3.2
|
||||
type-fest: ^3.5.1
|
||||
typescript: ^4.8.3
|
||||
v3-infinite-loading: ^1.2.2
|
||||
vee-validate: ^4.7.0
|
||||
vue-advanced-cropper: ^2.8.8
|
||||
vue-tippy: ^6.0.0
|
||||
@@ -11611,6 +11610,7 @@ __metadata:
|
||||
type-fest: ^2.13.1
|
||||
typescript: ^5.0.4
|
||||
unplugin-vue-macros: ^2.1.4
|
||||
v3-infinite-loading: ^1.2.2
|
||||
vee-validate: ^4.7.0
|
||||
vite: ^4.3.9
|
||||
vite-plugin-dts: ^2.3.0
|
||||
|
||||
Reference in New Issue
Block a user