mirror of
https://github.com/HoshinoSuzumi/rayine-ui.git
synced 2025-04-19 14:28:51 +08:00
Merge pull request #7 from HoshinoSuzumi/6-feat-new-component-mark
feat(mark): new component `RayMark`
This commit is contained in:
commit
c3147c2fd9
@ -24,7 +24,7 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
slots: {
|
slots: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({}),
|
default: null,
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
type: Array as PropType<{ name: string, values: string[], restriction: 'expected' | 'included' | 'excluded' | 'only' }[]>,
|
type: Array as PropType<{ name: string, values: string[], restriction: 'expected' | 'included' | 'excluded' | 'only' }[]>,
|
||||||
@ -119,7 +119,22 @@ const code = computed(() => {
|
|||||||
code += ` ${(typeof v === 'boolean' && (k === 'modelValue' || v !== true)) || typeof v === 'number' || typeof v === 'object' ? ':' : ''}${k === 'modelValue' ? 'model-value' : kebabCase(k)}${k !== 'modelValue' && typeof v === 'boolean' && !!v ? '' : `="${typeof v === 'object' ? renderObject(v) : v}"`}`
|
code += ` ${(typeof v === 'boolean' && (k === 'modelValue' || v !== true)) || typeof v === 'number' || typeof v === 'object' ? ':' : ''}${k === 'modelValue' ? 'model-value' : kebabCase(k)}${k !== 'modelValue' && typeof v === 'boolean' && !!v ? '' : `="${typeof v === 'object' ? renderObject(v) : v}"`}`
|
||||||
}
|
}
|
||||||
|
|
||||||
code += `/>\n</template>
|
if (props.slots) {
|
||||||
|
code += `>
|
||||||
|
${Object.entries(props.slots).map(([key, value]) => {
|
||||||
|
return key === 'default'
|
||||||
|
? value
|
||||||
|
: `<template #${key}>
|
||||||
|
${value}
|
||||||
|
</template>`
|
||||||
|
}).join('\n ')}
|
||||||
|
</${componentName}>`
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
code += ' />'
|
||||||
|
}
|
||||||
|
|
||||||
|
code += `\n</template>
|
||||||
\`\`\`
|
\`\`\`
|
||||||
`
|
`
|
||||||
return code
|
return code
|
||||||
@ -176,11 +191,12 @@ const { data: codeRender } = await useAsyncData(`${componentName}-code-renderer-
|
|||||||
v-else
|
v-else
|
||||||
:id="`${prop.name}-prop`"
|
:id="`${prop.name}-prop`"
|
||||||
:model-value="componentProps[prop.name]"
|
:model-value="componentProps[prop.name]"
|
||||||
:type="prop.type === 'number' ? 'number' : 'text'"
|
:type="prop.type.includes('number') ? 'number' : 'text'"
|
||||||
variant="plain"
|
variant="plain"
|
||||||
:padded="false"
|
:padded="false"
|
||||||
:ui="{ rounded: 'rounded-none' }"
|
:ui="{ rounded: 'rounded-none' }"
|
||||||
placeholder="type something..."
|
:placeholder="prop.type"
|
||||||
|
autocomplete="off"
|
||||||
@update:model-value="val => componentProps[prop.name] = prop.type === 'number' ? Number(val) : val"
|
@update:model-value="val => componentProps[prop.name] = prop.type === 'number' ? Number(val) : val"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -98,7 +98,7 @@ Button
|
|||||||
props:
|
props:
|
||||||
icon: tabler:adjustments
|
icon: tabler:adjustments
|
||||||
size: sm
|
size: sm
|
||||||
loading: true
|
loading: false
|
||||||
---
|
---
|
||||||
Settings
|
Settings
|
||||||
::
|
::
|
||||||
|
83
docs/content/2.components/mark.md
Normal file
83
docs/content/2.components/mark.md
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
---
|
||||||
|
description: Display a indicator with or without counts on any component
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Use the default slot to add any component you want to display the indicator on.
|
||||||
|
|
||||||
|
::ComponentPreview
|
||||||
|
---
|
||||||
|
slots:
|
||||||
|
default: |
|
||||||
|
<RayButton icon="tabler:message" label="messages" color="invert" />
|
||||||
|
---
|
||||||
|
#default
|
||||||
|
:RayButton{icon="tabler:message" label="messages" color="invert"}
|
||||||
|
::
|
||||||
|
|
||||||
|
### Styles
|
||||||
|
|
||||||
|
You can change the color and size of the indicator by using the `color` and `size` props.
|
||||||
|
|
||||||
|
::ComponentPreview
|
||||||
|
---
|
||||||
|
props:
|
||||||
|
color: amber
|
||||||
|
size: sm
|
||||||
|
slots:
|
||||||
|
default: |
|
||||||
|
<RayButton icon="tabler:message" label="messages" color="invert" />
|
||||||
|
---
|
||||||
|
#default
|
||||||
|
:RayButton{icon="tabler:message" label="messages" color="invert"}
|
||||||
|
::
|
||||||
|
|
||||||
|
### Position
|
||||||
|
|
||||||
|
Use the `position` prop to change the position of the indicator.
|
||||||
|
|
||||||
|
::ComponentPreview
|
||||||
|
---
|
||||||
|
props:
|
||||||
|
position: top-right
|
||||||
|
slots:
|
||||||
|
default: |
|
||||||
|
<RayButton icon="tabler:message" label="messages" color="invert" />
|
||||||
|
---
|
||||||
|
#default
|
||||||
|
:RayButton{icon="tabler:message" label="messages" color="invert"}
|
||||||
|
::
|
||||||
|
|
||||||
|
### Count
|
||||||
|
|
||||||
|
Add a count to the indicator by using the `value` prop.
|
||||||
|
|
||||||
|
::ComponentPreview
|
||||||
|
---
|
||||||
|
props:
|
||||||
|
value: 5
|
||||||
|
slots:
|
||||||
|
default: |
|
||||||
|
<RayButton icon="tabler:message" label="messages" color="invert" />
|
||||||
|
---
|
||||||
|
#default
|
||||||
|
:RayButton{icon="tabler:message" label="messages" color="invert"}
|
||||||
|
::
|
||||||
|
|
||||||
|
#### Overflow
|
||||||
|
|
||||||
|
Set `max` prop to handle overflow situation.
|
||||||
|
|
||||||
|
::ComponentPreview
|
||||||
|
---
|
||||||
|
props:
|
||||||
|
value: 110
|
||||||
|
max: 99
|
||||||
|
slots:
|
||||||
|
default: |
|
||||||
|
<RayButton icon="tabler:message" label="messages" color="invert" />
|
||||||
|
---
|
||||||
|
#default
|
||||||
|
:RayButton{icon="tabler:message" label="messages" color="invert"}
|
||||||
|
::
|
90
src/runtime/components/elements/Mark.vue
Normal file
90
src/runtime/components/elements/Mark.vue
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { computed, defineComponent, toRef, type PropType } from 'vue'
|
||||||
|
import { twJoin, twMerge } from 'tailwind-merge'
|
||||||
|
import { mark } from '../../ui.config'
|
||||||
|
import type { MarkColor, MarkPosition, MarkSize } from '../../types'
|
||||||
|
import { useRayUI } from '#build/imports'
|
||||||
|
|
||||||
|
const config = mark
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
inheritAttrs: false,
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
type: Number,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String as PropType<MarkSize>,
|
||||||
|
default: config.default.size,
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String as PropType<MarkColor>,
|
||||||
|
default: config.default.color,
|
||||||
|
},
|
||||||
|
position: {
|
||||||
|
type: String as PropType<MarkPosition>,
|
||||||
|
default: config.default.position,
|
||||||
|
},
|
||||||
|
ui: {
|
||||||
|
type: Object as PropType<typeof config>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
class: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const { ui, attrs } = useRayUI('mark', toRef(props, 'ui'), config)
|
||||||
|
|
||||||
|
const markClass = computed(() => {
|
||||||
|
return twMerge(twJoin(
|
||||||
|
ui.value.base,
|
||||||
|
ui.value.rounded,
|
||||||
|
ui.value.ring,
|
||||||
|
ui.value.position[props.position],
|
||||||
|
ui.value.background.replaceAll('{color}', props.color),
|
||||||
|
props.value ? ui.value.value.size[props.size] : ui.value.size[props.size],
|
||||||
|
props.value ? ui.value.value.translate[props.position] : ui.value.translate[props.position],
|
||||||
|
), props.class)
|
||||||
|
})
|
||||||
|
|
||||||
|
const isOverMax = computed(() => {
|
||||||
|
if (props.max === null) return false
|
||||||
|
if (typeof props.value === 'string') return false
|
||||||
|
return props.value > props.max
|
||||||
|
})
|
||||||
|
|
||||||
|
// consider string value
|
||||||
|
const countValue = computed(() => {
|
||||||
|
if (typeof props.value === 'string') return props.value
|
||||||
|
return isOverMax.value ? `${props.max}+` : props.value
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
// eslint-disable-next-line vue/no-dupe-keys
|
||||||
|
ui,
|
||||||
|
attrs,
|
||||||
|
markClass,
|
||||||
|
isOverMax,
|
||||||
|
countValue,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="ui.wrapper">
|
||||||
|
<span :class="markClass">
|
||||||
|
<Transition v-bind="ui.transition">
|
||||||
|
<span v-if="value" :key="countValue" class="leading-none">{{ countValue }}</span>
|
||||||
|
</Transition>
|
||||||
|
</span>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -4,5 +4,6 @@ export * from './input'
|
|||||||
export * from './textarea'
|
export * from './textarea'
|
||||||
export * from './kbd'
|
export * from './kbd'
|
||||||
export * from './toggle'
|
export * from './toggle'
|
||||||
|
export * from './mark'
|
||||||
|
|
||||||
export * from './utils'
|
export * from './utils'
|
||||||
|
14
src/runtime/types/mark.d.ts
vendored
Normal file
14
src/runtime/types/mark.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import type { AppConfig } from '@nuxt/schema'
|
||||||
|
import type { mark } from '../ui.config'
|
||||||
|
import type { ExtractDeepKey } from './utils'
|
||||||
|
import type colors from '#ray-colors'
|
||||||
|
|
||||||
|
export type MarkSize =
|
||||||
|
| keyof typeof mark.size
|
||||||
|
| ExtractDeepKey<AppConfig, ['rayui', 'mark', 'size']>
|
||||||
|
export type MarkColor =
|
||||||
|
| ExtractDeepKey<AppConfig, ['rayui', 'mark', 'color']>
|
||||||
|
| (typeof colors)[number]
|
||||||
|
export type MarkPosition =
|
||||||
|
| keyof typeof mark.position
|
||||||
|
| ExtractDeepKey<AppConfig, ['rayui', 'mark', 'position']>
|
51
src/runtime/ui.config/elements/mark.ts
Normal file
51
src/runtime/ui.config/elements/mark.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
export default {
|
||||||
|
wrapper: 'relative',
|
||||||
|
base: 'absolute text-white rounded-full inline-flex justify-center items-center',
|
||||||
|
ring: 'ring-2 ring-white dark:ring-gray-900',
|
||||||
|
rounded: 'rounded-full',
|
||||||
|
background: 'bg-{color}-500',
|
||||||
|
position: {
|
||||||
|
'top-left': 'top-0 left-0',
|
||||||
|
'top-right': 'top-0 right-0',
|
||||||
|
'bottom-left': 'bottom-0 left-0',
|
||||||
|
'bottom-right': 'bottom-0 right-0',
|
||||||
|
},
|
||||||
|
translate: {
|
||||||
|
'top-left': '-translate-x-0.5 -translate-y-0.5',
|
||||||
|
'top-right': 'translate-x-0.5 -translate-y-0.5',
|
||||||
|
'bottom-left': '-translate-x-0.5 translate-y-0.5',
|
||||||
|
'bottom-right': 'translate-x-0.5 translate-y-0.5',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
xs: 'w-1.5 h-1.5',
|
||||||
|
sm: 'w-2 h-2',
|
||||||
|
md: 'w-2.5 h-2.5',
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
size: {
|
||||||
|
xs: 'px-1 h-3 leading-none text-xs',
|
||||||
|
sm: 'px-1.5 h-4 leading-none text-xs',
|
||||||
|
md: 'px-2 h-5 leading-none text-sm',
|
||||||
|
},
|
||||||
|
translate: {
|
||||||
|
'top-left': '-translate-x-1/3 -translate-y-1/3',
|
||||||
|
'top-right': 'translate-x-1/3 -translate-y-1/3',
|
||||||
|
'bottom-left': '-translate-x-1/3 translate-y-1/3',
|
||||||
|
'bottom-right': 'translate-x-1/3 translate-y-1/3',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
transition: {
|
||||||
|
moveClass: 'transform ease-out duration-300 transition',
|
||||||
|
enterActiveClass: 'transform ease-out duration-300 transition',
|
||||||
|
leaveActiveClass: 'transform ease-out duration-300 transition absolute',
|
||||||
|
enterFromClass: 'translate-y-2 opacity-0',
|
||||||
|
enterToClass: 'translate-y-0 opacity-100',
|
||||||
|
leaveFromClass: 'translate-y-0 opacity-100',
|
||||||
|
leaveToClass: '-translate-y-2 opacity-0',
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
size: 'sm',
|
||||||
|
color: 'primary',
|
||||||
|
position: 'top-right',
|
||||||
|
},
|
||||||
|
}
|
@ -4,6 +4,7 @@ export { default as standard } from './standard'
|
|||||||
// elements
|
// elements
|
||||||
export { default as button } from './elements/button'
|
export { default as button } from './elements/button'
|
||||||
export { default as kbd } from './elements/kbd'
|
export { default as kbd } from './elements/kbd'
|
||||||
|
export { default as mark } from './elements/mark'
|
||||||
|
|
||||||
// forms
|
// forms
|
||||||
export { default as input } from './forms/input'
|
export { default as input } from './forms/input'
|
||||||
|
Loading…
Reference in New Issue
Block a user