mirror of
https://github.com/HoshinoSuzumi/rayine-ui.git
synced 2025-04-10 04:58:51 +08:00
✨ feat(button): add disabled and loading state
This commit is contained in:
parent
d913f91644
commit
ed419eff25
@ -3,7 +3,7 @@ title: Introduction
|
|||||||
description: Multi-purpose customizable components for RayineSoft projects
|
description: Multi-purpose customizable components for RayineSoft projects
|
||||||
---
|
---
|
||||||
|
|
||||||
Rayine UI is a collection of multi-purpose customizable components for RayineSoft projects.
|
RayineUI is a multi-purpose customizable UI library for RayineSoft projects.
|
||||||
|
|
||||||
This project aims to facilitate sharing a component library across multiple projects for my own use. Open-sourcing it is just a bonus. Therefore, I am under no obligation to meet your requirements, and breaking changes may occur at any time. Of course, pull requests are welcome.
|
This project aims to facilitate sharing a component library across multiple projects for my own use. Open-sourcing it is just a bonus. Therefore, I am under no obligation to meet your requirements, and breaking changes may occur at any time. Of course, pull requests are welcome.
|
||||||
|
|
||||||
|
@ -12,6 +12,8 @@ Button
|
|||||||
|
|
||||||
### Variants
|
### Variants
|
||||||
|
|
||||||
|
#### soft
|
||||||
|
|
||||||
::ComponentPreview
|
::ComponentPreview
|
||||||
---
|
---
|
||||||
props:
|
props:
|
||||||
@ -20,6 +22,36 @@ props:
|
|||||||
Button
|
Button
|
||||||
::
|
::
|
||||||
|
|
||||||
|
#### outline
|
||||||
|
|
||||||
|
::ComponentPreview
|
||||||
|
---
|
||||||
|
props:
|
||||||
|
variant: outline
|
||||||
|
---
|
||||||
|
Button
|
||||||
|
::
|
||||||
|
|
||||||
|
#### ghost
|
||||||
|
|
||||||
|
::ComponentPreview
|
||||||
|
---
|
||||||
|
props:
|
||||||
|
variant: ghost
|
||||||
|
---
|
||||||
|
Button
|
||||||
|
::
|
||||||
|
|
||||||
|
#### link
|
||||||
|
|
||||||
|
::ComponentPreview
|
||||||
|
---
|
||||||
|
props:
|
||||||
|
variant: link
|
||||||
|
---
|
||||||
|
Button
|
||||||
|
::
|
||||||
|
|
||||||
### Colors
|
### Colors
|
||||||
|
|
||||||
::ComponentPreview
|
::ComponentPreview
|
||||||
@ -29,3 +61,23 @@ props:
|
|||||||
---
|
---
|
||||||
Button
|
Button
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### Disabled
|
||||||
|
|
||||||
|
::ComponentPreview
|
||||||
|
---
|
||||||
|
props:
|
||||||
|
disabled: true
|
||||||
|
---
|
||||||
|
Button
|
||||||
|
::
|
||||||
|
|
||||||
|
### Loading
|
||||||
|
|
||||||
|
::ComponentPreview
|
||||||
|
---
|
||||||
|
props:
|
||||||
|
loading: true
|
||||||
|
---
|
||||||
|
Button
|
||||||
|
::
|
||||||
|
@ -20,10 +20,10 @@ const router = useRouter()
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center items-center gap-4">
|
<div class="flex justify-center items-center gap-4">
|
||||||
<RayButton @click="router.push('/getting-started')">
|
<RayButton to="/getting-started">
|
||||||
Getting Started
|
Getting Started
|
||||||
</RayButton>
|
</RayButton>
|
||||||
<RayButton variant="outline" @click="router.push('/components')">
|
<RayButton variant="outline" to="/components">
|
||||||
Explore Components
|
Explore Components
|
||||||
</RayButton>
|
</RayButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,18 +1,27 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { twJoin, twMerge } from 'tailwind-merge'
|
import { twJoin, twMerge } from 'tailwind-merge'
|
||||||
import { computed, toRef, type PropType } from 'vue'
|
import { computed, toRef, type PropType } from 'vue'
|
||||||
import { button } from '../../ui.config'
|
|
||||||
import type { DeepPartial, Strategy } from '../../types/utils'
|
import type { DeepPartial, Strategy } from '../../types/utils'
|
||||||
import type { ButtonColor, ButtonSize, ButtonVariant } from '../../types/button'
|
import type { ButtonColor, ButtonSize, ButtonVariant } from '../../types/button'
|
||||||
|
import { getNonUndefinedValuesFromObject } from '../../utils'
|
||||||
|
import { nuxtLinkProps } from '../../utils/link'
|
||||||
|
import { button } from '../../ui.config'
|
||||||
import { useRayUI } from '#build/imports'
|
import { useRayUI } from '#build/imports'
|
||||||
|
|
||||||
const config = button
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
...nuxtLinkProps,
|
||||||
class: {
|
class: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
padded: {
|
padded: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
@ -25,6 +34,14 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
to: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
size: {
|
size: {
|
||||||
type: String as PropType<ButtonSize>,
|
type: String as PropType<ButtonSize>,
|
||||||
default: () => button.default.size,
|
default: () => button.default.size,
|
||||||
@ -37,13 +54,19 @@ const props = defineProps({
|
|||||||
type: String as PropType<ButtonVariant>,
|
type: String as PropType<ButtonVariant>,
|
||||||
default: () => button.default.variant,
|
default: () => button.default.variant,
|
||||||
},
|
},
|
||||||
|
loadingIcon: {
|
||||||
|
type: String,
|
||||||
|
default: () => button.default.loadingIcon,
|
||||||
|
},
|
||||||
ui: {
|
ui: {
|
||||||
type: Object as PropType<DeepPartial<typeof config> & { strategy?: Strategy }>,
|
type: Object as PropType<DeepPartial<typeof button> & { strategy?: Strategy }>,
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const { ui, attrs } = useRayUI('button', toRef(props, 'ui'), config)
|
const extProps = computed(() => getNonUndefinedValuesFromObject(props, nuxtLinkProps))
|
||||||
|
|
||||||
|
const { ui, attrs } = useRayUI('button', toRef(props, 'ui'), button)
|
||||||
|
|
||||||
const buttonClass = computed(() => {
|
const buttonClass = computed(() => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -61,12 +84,14 @@ const buttonClass = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<RayLink type="button" :disabled="disabled || loading" :class="buttonClass" v-bind="{ ...extProps, ...attrs }">
|
||||||
:class="buttonClass"
|
<slot name="leading" :disabled="disabled" :loading="loading">
|
||||||
v-bind="{ ...attrs }"
|
<IconSpinner v-if="loading" class="mr-1" />
|
||||||
>
|
</slot>
|
||||||
<slot />
|
<slot>
|
||||||
</button>
|
<span v-if="label">{{ label }}</span>
|
||||||
|
</slot>
|
||||||
|
</RayLink>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
65
src/runtime/components/elements/Link.vue
Normal file
65
src/runtime/components/elements/Link.vue
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { nuxtLinkProps } from '../../utils/link'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
...nuxtLinkProps,
|
||||||
|
to: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
as: {
|
||||||
|
type: String,
|
||||||
|
default: 'button',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'button',
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
type: Boolean,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
activeClass: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
inactiveClass: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<component
|
||||||
|
:is="as"
|
||||||
|
v-if="!to"
|
||||||
|
:type="type"
|
||||||
|
:class="active ? activeClass : inactiveClass"
|
||||||
|
:disabled="disabled"
|
||||||
|
v-bind="$attrs"
|
||||||
|
>
|
||||||
|
<slot v-bind="{ isActive: active }" />
|
||||||
|
</component>
|
||||||
|
<NuxtLink v-else v-slot="{ href, target, rel, navigate, isActive, isExternal }" v-bind="props" custom>
|
||||||
|
<a
|
||||||
|
v-bind="$attrs"
|
||||||
|
:href="!disabled ? href : undefined"
|
||||||
|
:aria-disabled="disabled ? 'true' : undefined"
|
||||||
|
:role="disabled ? 'link' : undefined"
|
||||||
|
:rel="rel"
|
||||||
|
:target="target"
|
||||||
|
:class="active !== undefined ? (active ? activeClass : inactiveClass) : { [activeClass]: isActive, [inactiveClass]: !isActive }"
|
||||||
|
:tabindex="!disabled ? undefined : -1"
|
||||||
|
@click="(e) => (!disabled && !isExternal) && navigate(e)"
|
||||||
|
>
|
||||||
|
<slot v-bind="{ isActive: active !== undefined ? active : isActive }" />
|
||||||
|
</a>
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
@ -28,6 +28,18 @@ export default {
|
|||||||
'lg': 'p-2.5',
|
'lg': 'p-2.5',
|
||||||
'xl': 'p-2.5',
|
'xl': 'p-2.5',
|
||||||
},
|
},
|
||||||
|
icon: {
|
||||||
|
base: 'flex-shrink-0',
|
||||||
|
loading: 'animate-spin',
|
||||||
|
size: {
|
||||||
|
'2xs': 'h-4 w-4',
|
||||||
|
'xs': 'h-4 w-4',
|
||||||
|
'sm': 'h-5 w-5',
|
||||||
|
'md': 'h-5 w-5',
|
||||||
|
'lg': 'h-5 w-5',
|
||||||
|
'xl': 'h-6 w-6',
|
||||||
|
},
|
||||||
|
},
|
||||||
color: {},
|
color: {},
|
||||||
variant: {
|
variant: {
|
||||||
solid:
|
solid:
|
||||||
@ -43,5 +55,6 @@ export default {
|
|||||||
size: 'sm',
|
size: 'sm',
|
||||||
color: 'primary',
|
color: 'primary',
|
||||||
variant: 'solid',
|
variant: 'solid',
|
||||||
|
loadingIcon: 'loading',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
83
src/runtime/utils/link.ts
Normal file
83
src/runtime/utils/link.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import type { PropType } from 'vue'
|
||||||
|
import type { RouteLocationRaw } from '#vue-router'
|
||||||
|
import type { NuxtLinkProps } from '#app'
|
||||||
|
|
||||||
|
// NuxtLink props
|
||||||
|
// ref: https://github.com/nuxt/ui/blob/51c8b8e3e59d7eceff72625650a199fcf7c6feca/src/runtime/utils/link.ts#L5-L81
|
||||||
|
export const nuxtLinkProps = {
|
||||||
|
to: {
|
||||||
|
type: [String, Object] as PropType<RouteLocationRaw>,
|
||||||
|
default: undefined,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
href: {
|
||||||
|
type: [String, Object] as PropType<RouteLocationRaw>,
|
||||||
|
default: undefined,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Attributes
|
||||||
|
target: {
|
||||||
|
type: String as PropType<NuxtLinkProps['target']>,
|
||||||
|
default: undefined,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
rel: {
|
||||||
|
type: String as PropType<any>,
|
||||||
|
default: undefined,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
noRel: {
|
||||||
|
type: Boolean as PropType<NuxtLinkProps['noRel']>,
|
||||||
|
default: undefined,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Prefetching
|
||||||
|
prefetch: {
|
||||||
|
type: Boolean as PropType<NuxtLinkProps['prefetch']>,
|
||||||
|
default: undefined,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
noPrefetch: {
|
||||||
|
type: Boolean as PropType<NuxtLinkProps['noPrefetch']>,
|
||||||
|
default: undefined,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Styling
|
||||||
|
activeClass: {
|
||||||
|
type: String as PropType<NuxtLinkProps['activeClass']>,
|
||||||
|
default: undefined,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
exactActiveClass: {
|
||||||
|
type: String as PropType<NuxtLinkProps['exactActiveClass']>,
|
||||||
|
default: undefined,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
prefetchedClass: {
|
||||||
|
type: String as PropType<NuxtLinkProps['prefetchedClass']>,
|
||||||
|
default: undefined,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Vue Router's `<RouterLink>` additional props
|
||||||
|
replace: {
|
||||||
|
type: Boolean as PropType<NuxtLinkProps['replace']>,
|
||||||
|
default: undefined,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
ariaCurrentValue: {
|
||||||
|
type: String as PropType<NuxtLinkProps['ariaCurrentValue']>,
|
||||||
|
default: undefined,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Edge cases handling
|
||||||
|
external: {
|
||||||
|
type: Boolean as PropType<NuxtLinkProps['external']>,
|
||||||
|
default: undefined,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
} as const
|
@ -36,3 +36,18 @@ export const getValueByPath = (
|
|||||||
|
|
||||||
return result !== undefined ? result : defaultValue
|
return result !== undefined ? result : defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getNonUndefinedValuesFromObject = (
|
||||||
|
obj: object,
|
||||||
|
srcObj: object,
|
||||||
|
): object => {
|
||||||
|
const keys = Object.keys(srcObj) as []
|
||||||
|
|
||||||
|
return keys.reduce((acc, key) => {
|
||||||
|
if (obj[key] !== undefined) {
|
||||||
|
acc[key] = obj[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user