mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 12:14:41 +01:00
feat: support presets (#14)
This commit is contained in:
@@ -2,6 +2,7 @@ export default {
|
||||
entries: [
|
||||
'./src/index',
|
||||
{ input: './src/components/', outDir: 'dist/components', ext: 'js' },
|
||||
{ input: './src/presets/', outDir: 'dist/presets', ext: 'js' },
|
||||
{ input: './src/utils/', outDir: 'dist/utils', ext: 'js' }
|
||||
],
|
||||
declaration: true,
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
import { UseDark } from '@vueuse/components'
|
||||
|
||||
const sections = [
|
||||
{ label: 'Getting Started', links: [{ label: 'Installation', to: '/' }, { label: 'Examples', to: '/examples' }, { label: 'Migration', to: '/migration' }, { label: 'Dark mode', to: '/dark' }] },
|
||||
{ label: 'Getting Started', links: [{ label: 'Usage', to: '/' }, { label: 'Examples', to: '/examples' }, { label: 'Migration', to: '/migration' }, { label: 'Dark mode', to: '/dark' }] },
|
||||
{ label: 'Elements', links: [{ label: 'Avatar', to: '/components/Avatar' }, { label: 'AvatarGroup', to: '/components/AvatarGroup' }, { label: 'Badge', to: '/components/Badge' }, { label: 'Button', to: '/components/Button' }, { label: 'Dropdown', to: '/components/Dropdown' }, { label: 'Icon', to: '/components/Icon' }, { label: 'Link', to: '/components/Link' }, { label: 'Toggle', to: '/components/Toggle' }] },
|
||||
{ label: 'Feedback', links: [{ label: 'Alert', to: '/components/Alert' }] },
|
||||
{ label: 'Forms', links: [{ label: 'Checkbox', to: '/components/Checkbox' }, { label: 'Input', to: '/components/Input' }, { label: 'InputGroup', to: '/components/InputGroup' }, { label: 'Radio', to: '/components/Radio' }, { label: 'RadioGroup', to: '/components/RadioGroup' }, { label: 'Select', to: '/components/Select' }, { label: 'SelectCustom', to: '/components/SelectCustom' }, { label: 'Textarea', to: '/components/Textarea' }] },
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import $ui from '#build/ui'
|
||||
|
||||
const nuxtApp = useNuxtApp()
|
||||
const { params } = useRoute()
|
||||
|
||||
@@ -74,8 +76,15 @@ const refProps = Object.entries(componentProps).map(([key, prop]) => {
|
||||
|
||||
let values
|
||||
if (prop.validator) {
|
||||
const result = prop.validator.toString().match(/\[.*\]/g, '')[0]
|
||||
values = JSON.parse(result.replace(/'/g, '"')).filter(Boolean)
|
||||
const arrayRegex = prop.validator.toString().match(/\[.*\]/g, '')
|
||||
if (arrayRegex) {
|
||||
values = JSON.parse(arrayRegex[0].replace(/'/g, '"')).filter(Boolean)
|
||||
} else {
|
||||
const $uiProp = $ui[params.component.toLowerCase()][key]
|
||||
if ($uiProp) {
|
||||
values = Object.keys($uiProp).filter(Boolean)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (value) {
|
||||
|
||||
@@ -49,6 +49,10 @@ export default defineNuxtConfig({
|
||||
Options
|
||||
</h2>
|
||||
|
||||
<p>- `preset`</p>
|
||||
|
||||
<p>Choose preset. Defaults to `tailwindui`. An object can also be used to override some parts of the default preset.</p>
|
||||
|
||||
<p>- `prefix`</p>
|
||||
|
||||
<p>Define the prefix of the imported components. Defaults to `u`.</p>
|
||||
|
||||
@@ -20,7 +20,10 @@
|
||||
Component
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium u-text-gray-500 uppercase tracking-wider">
|
||||
Ready?
|
||||
Composition API
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium u-text-gray-500 uppercase tracking-wider">
|
||||
Preset system
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -32,7 +35,11 @@
|
||||
</NuxtLink>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm u-text-gray-500">
|
||||
<span v-if="component.ready">✅</span>
|
||||
<span v-if="component.capi">✅</span>
|
||||
<span v-else>❌</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm u-text-gray-500">
|
||||
<span v-if="component.preset">✅</span>
|
||||
<span v-else>❌</span>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -63,12 +70,12 @@ const components = [
|
||||
{
|
||||
label: 'Dropdown',
|
||||
to: '/components/Dropdown',
|
||||
ready: true
|
||||
capi: true
|
||||
},
|
||||
{
|
||||
label: 'Icon',
|
||||
to: '/components/Icon',
|
||||
ready: true
|
||||
capi: true
|
||||
},
|
||||
{
|
||||
label: 'Link',
|
||||
@@ -89,7 +96,8 @@ const components = [
|
||||
{
|
||||
label: 'Input',
|
||||
to: '/components/Input',
|
||||
ready: true
|
||||
capi: true,
|
||||
preset: true
|
||||
},
|
||||
{
|
||||
label: 'InputGroup',
|
||||
@@ -105,7 +113,9 @@ const components = [
|
||||
},
|
||||
{
|
||||
label: 'Select',
|
||||
to: '/components/Select'
|
||||
to: '/components/Select',
|
||||
capi: true,
|
||||
preset: true
|
||||
},
|
||||
{
|
||||
label: 'SelectCustom',
|
||||
@@ -114,17 +124,18 @@ const components = [
|
||||
{
|
||||
label: 'Textarea',
|
||||
to: '/components/Textarea',
|
||||
ready: true
|
||||
capi: true,
|
||||
preset: true
|
||||
},
|
||||
{
|
||||
label: 'Card',
|
||||
to: '/components/Card',
|
||||
ready: true
|
||||
capi: true
|
||||
},
|
||||
{
|
||||
label: 'Container',
|
||||
to: '/components/Container',
|
||||
ready: true
|
||||
capi: true
|
||||
},
|
||||
{
|
||||
label: 'Pills',
|
||||
@@ -141,7 +152,7 @@ const components = [
|
||||
{
|
||||
label: 'Modal',
|
||||
to: '/components/Modal',
|
||||
ready: true
|
||||
capi: true
|
||||
},
|
||||
{
|
||||
label: 'Notification',
|
||||
@@ -150,7 +161,7 @@ const components = [
|
||||
{
|
||||
label: 'Popover',
|
||||
to: '/components/Popover',
|
||||
ready: true
|
||||
capi: true
|
||||
},
|
||||
{
|
||||
label: 'Slideover',
|
||||
|
||||
@@ -28,13 +28,15 @@
|
||||
"@iconify-json/heroicons-solid": "^1.0.2",
|
||||
"@popperjs/core": "^2.10.2",
|
||||
"@unocss/nuxt": "^0.12.9",
|
||||
"defu": "^5.0.0",
|
||||
"gradient-avatar": "^1.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"pathe": "^0.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vueuse/core": "^7.1.1",
|
||||
"@vueuse/components": "^7.1.1",
|
||||
"@nuxtjs/eslint-config-typescript": "7.0.2",
|
||||
"@vueuse/components": "^7.1.1",
|
||||
"@vueuse/core": "^7.1.1",
|
||||
"eslint": "8.3.0",
|
||||
"nuxt3": "3.0.0-27296423.f3082ca",
|
||||
"unbuild": "0.5.13"
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
<template>
|
||||
<div :class="wrapperClass">
|
||||
<div
|
||||
v-if="isLeading"
|
||||
class="absolute inset-y-0 left-0 flex items-center pointer-events-none"
|
||||
>
|
||||
<Icon
|
||||
:name="iconName"
|
||||
class="u-text-gray-400"
|
||||
:class="iconClass"
|
||||
/>
|
||||
<div v-if="isLeading" :class="iconLeadingWrapperClass">
|
||||
<Icon :name="iconName" :class="iconClass" />
|
||||
</div>
|
||||
<input
|
||||
:id="name"
|
||||
@@ -22,21 +15,14 @@
|
||||
:readonly="readonly"
|
||||
:autocomplete="autocomplete"
|
||||
:spellcheck="spellcheck"
|
||||
:class="[baseClass, sizeClass, paddingClass, paddingIconClass, appearanceClass, customClass]"
|
||||
:class="inputClass"
|
||||
@input="onInput($event.target.value)"
|
||||
@focus="$emit('focus', $event)"
|
||||
@blur="$emit('blur', $event)"
|
||||
>
|
||||
<slot />
|
||||
<div
|
||||
v-if="isTrailing"
|
||||
class="absolute inset-y-0 right-0 flex items-center pointer-events-none"
|
||||
>
|
||||
<Icon
|
||||
:name="iconName"
|
||||
class="u-text-gray-400"
|
||||
:class="iconClass"
|
||||
/>
|
||||
<div v-if="isTrailing" :class="iconTrailingWrapperClass">
|
||||
<Icon :name="iconName" :class="iconClass" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -44,6 +30,8 @@
|
||||
<script>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import Icon from '../elements/Icon'
|
||||
import { classNames } from '../../utils'
|
||||
import $ui from '#build/ui'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -96,7 +84,7 @@ export default {
|
||||
},
|
||||
loadingIcon: {
|
||||
type: String,
|
||||
default: null
|
||||
default: 'heroicons-outline:refresh'
|
||||
},
|
||||
trailing: {
|
||||
type: Boolean,
|
||||
@@ -110,16 +98,20 @@ export default {
|
||||
type: String,
|
||||
default: 'md',
|
||||
validator (value) {
|
||||
return ['', 'xxs', 'xs', 'sm', 'md', 'lg', 'xl'].includes(value)
|
||||
return Object.keys($ui.input.size).includes(value)
|
||||
}
|
||||
},
|
||||
wrapperClass: {
|
||||
type: String,
|
||||
default: 'relative'
|
||||
default: $ui.input.wrapper
|
||||
},
|
||||
baseClass: {
|
||||
type: String,
|
||||
default: 'block w-full u-bg-white u-text-gray-700 disabled:cursor-not-allowed disabled:u-bg-gray-50 focus:outline-none'
|
||||
default: () => $ui.input.base
|
||||
},
|
||||
iconBaseClass: {
|
||||
type: String,
|
||||
default: () => $ui.input.icon.base
|
||||
},
|
||||
customClass: {
|
||||
type: String,
|
||||
@@ -129,7 +121,7 @@ export default {
|
||||
type: String,
|
||||
default: 'default',
|
||||
validator (value) {
|
||||
return ['default', 'none'].includes(value)
|
||||
return Object.keys($ui.input.appearance).includes(value)
|
||||
}
|
||||
},
|
||||
loading: {
|
||||
@@ -157,50 +149,6 @@ export default {
|
||||
}, 100)
|
||||
})
|
||||
|
||||
const sizeClass = computed(() => ({
|
||||
xxs: 'text-xs',
|
||||
xs: 'text-xs',
|
||||
sm: 'text-sm leading-4',
|
||||
md: 'text-sm',
|
||||
lg: 'text-base',
|
||||
xl: 'text-base'
|
||||
})[props.size])
|
||||
|
||||
const paddingClass = computed(() => ({
|
||||
xxs: 'px-1 py-0.5',
|
||||
xs: 'px-2.5 py-1.5',
|
||||
sm: 'px-3 py-2',
|
||||
md: 'px-4 py-2',
|
||||
lg: 'px-4 py-2',
|
||||
xl: 'px-6 py-3'
|
||||
})[props.size])
|
||||
|
||||
const appearanceClass = computed(() => ({
|
||||
default: 'focus:ring-1 focus:ring-primary-500 focus:border-primary-500 border u-border-gray-300 rounded-md shadow-sm',
|
||||
none: 'border-0 bg-transparent focus:ring-0 focus:shadow-none'
|
||||
})[props.appearance])
|
||||
|
||||
const paddingIconClass = computed(() => {
|
||||
return [
|
||||
props.isLeading && ({
|
||||
xxs: 'pl-7',
|
||||
xs: 'pl-7',
|
||||
sm: 'pl-10',
|
||||
md: 'pl-10',
|
||||
lg: 'pl-10',
|
||||
xl: 'pl-10'
|
||||
})[props.size],
|
||||
props.isTrailing && ({
|
||||
xxs: 'pr-10',
|
||||
xs: 'pr-10',
|
||||
sm: 'pr-10',
|
||||
md: 'pr-10',
|
||||
lg: 'pr-10',
|
||||
xl: 'pr-10'
|
||||
})[props.size]
|
||||
].join(' ')
|
||||
})
|
||||
|
||||
const isLeading = computed(() => {
|
||||
return (props.icon && props.leading) || (props.icon && !props.trailing) || (props.loading && !props.trailing)
|
||||
})
|
||||
@@ -209,55 +157,47 @@ export default {
|
||||
return (props.icon && props.trailing) || (props.loading && props.trailing)
|
||||
})
|
||||
|
||||
const inputClass = computed(() => {
|
||||
return classNames(
|
||||
props.baseClass,
|
||||
$ui.input.size[props.size],
|
||||
$ui.input.spacing[props.size],
|
||||
$ui.input.appearance[props.appearance],
|
||||
isLeading.value && $ui.input.leading.spacing[props.size],
|
||||
isTrailing.value && $ui.input.trailing.spacing[props.size],
|
||||
props.customClass
|
||||
)
|
||||
})
|
||||
|
||||
const iconName = computed(() => {
|
||||
if (props.loading) {
|
||||
return props.loadingIcon || 'custom/loading'
|
||||
return props.loadingIcon
|
||||
}
|
||||
|
||||
return props.icon
|
||||
})
|
||||
|
||||
const iconClass = computed(() => {
|
||||
return [
|
||||
({
|
||||
xxs: 'h-3 w-3',
|
||||
xs: 'h-4 w-4',
|
||||
sm: 'h-5 w-5',
|
||||
md: 'h-5 w-5',
|
||||
lg: 'h-5 w-5',
|
||||
xl: 'h-5 w-5'
|
||||
})[props.size || 'sm'],
|
||||
props.isLeading && ({
|
||||
xxs: 'ml-2',
|
||||
xs: 'ml-2',
|
||||
sm: 'ml-3',
|
||||
md: 'ml-3',
|
||||
lg: 'ml-3',
|
||||
xl: 'ml-3'
|
||||
})[props.size || 'sm'],
|
||||
props.isTrailing && ({
|
||||
xxs: 'mr-2',
|
||||
xs: 'mr-2',
|
||||
sm: 'mr-3',
|
||||
md: 'mr-3',
|
||||
lg: 'mr-3',
|
||||
xl: 'mr-3'
|
||||
})[props.size || 'sm'],
|
||||
({
|
||||
true: 'animate-spin'
|
||||
})[props.loading]
|
||||
]
|
||||
return classNames(
|
||||
props.iconBaseClass,
|
||||
$ui.input.icon.size[props.size],
|
||||
isLeading.value && $ui.input.icon.leading.spacing[props.size],
|
||||
isTrailing.value && $ui.input.icon.trailing.spacing[props.size],
|
||||
props.loading && 'animate-spin'
|
||||
)
|
||||
})
|
||||
|
||||
const iconLeadingWrapperClass = $ui.input.icon.leading.wrapper
|
||||
const iconTrailingWrapperClass = $ui.input.icon.trailing.wrapper
|
||||
|
||||
return {
|
||||
input,
|
||||
onInput,
|
||||
sizeClass,
|
||||
paddingClass,
|
||||
paddingIconClass,
|
||||
appearanceClass,
|
||||
iconClass,
|
||||
inputClass,
|
||||
iconName,
|
||||
iconClass,
|
||||
iconLeadingWrapperClass,
|
||||
iconTrailingWrapperClass,
|
||||
isLeading,
|
||||
isTrailing
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
<template>
|
||||
<div :class="wrapperClass">
|
||||
<div v-if="icon" :class="iconWrapperClass">
|
||||
<Icon :name="icon" :class="iconClass" />
|
||||
</div>
|
||||
|
||||
<select
|
||||
:id="name"
|
||||
:name="name"
|
||||
:required="required"
|
||||
:disabled="disabled"
|
||||
:readonly="readonly"
|
||||
:class="selectClass"
|
||||
@input="updateValue($event.target.value)"
|
||||
@input="onInput($event.target.value)"
|
||||
>
|
||||
<template v-for="(option, index) in normalizedOptionsWithPlaceholder">
|
||||
<optgroup
|
||||
@@ -33,16 +36,14 @@
|
||||
/>
|
||||
</template>
|
||||
</select>
|
||||
<div v-if="icon" class="absolute inset-y-0 left-0 flex items-center pointer-events-none" :class="iconPadding">
|
||||
<Icon :name="icon" :class="iconClass" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import get from 'lodash/get'
|
||||
|
||||
import Icon from '../elements/Icon'
|
||||
import { classNames } from '../../utils'
|
||||
import $ui from '#build/ui'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -73,24 +74,24 @@ export default {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'md',
|
||||
validator (value) {
|
||||
return ['xxs', 'xs', 'sm', 'md', 'lg', 'xl'].includes(value)
|
||||
return Object.keys($ui.select.size).includes(value)
|
||||
}
|
||||
},
|
||||
wrapperClass: {
|
||||
type: String,
|
||||
default: 'relative'
|
||||
default: $ui.select.wrapper
|
||||
},
|
||||
baseClass: {
|
||||
type: String,
|
||||
default: 'block w-full disabled:cursor-not-allowed u-bg-white u-text-gray-700 disabled:u-bg-gray-50 focus:ring-1 focus:ring-primary-500 focus:border-primary-500 dark:focus:border-primary-500 border u-border-gray-300 rounded-md shadow-sm focus:outline-none'
|
||||
default: $ui.select.base
|
||||
},
|
||||
iconBaseClass: {
|
||||
type: String,
|
||||
default: $ui.select.icon.base
|
||||
},
|
||||
customClass: {
|
||||
type: String,
|
||||
@@ -110,101 +111,97 @@ export default {
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
computed: {
|
||||
sizeClass () {
|
||||
return {
|
||||
xxs: 'text-xs',
|
||||
xs: 'text-xs',
|
||||
sm: 'text-sm leading-4',
|
||||
md: 'text-sm',
|
||||
lg: 'text-base',
|
||||
xl: 'text-base'
|
||||
}[this.size]
|
||||
},
|
||||
paddingClass () {
|
||||
return ({
|
||||
xxs: `${this.icon ? 'pl-7' : 'pl-2'} pr-7 py-1.5`,
|
||||
xs: `${this.icon ? 'pl-8' : 'pl-3'} pr-9 py-1.5`,
|
||||
sm: `${this.icon ? 'pl-8' : 'pl-3'} pr-9 py-2`,
|
||||
md: `${this.icon ? 'pl-10' : 'pl-3'} pr-10 py-2`,
|
||||
lg: `${this.icon ? 'pl-10' : 'pl-3'} pr-10 py-2`,
|
||||
xl: `${this.icon ? 'pl-12' : 'pl-4'} pr-12 py-3`
|
||||
})[this.size]
|
||||
},
|
||||
iconClass () {
|
||||
return ({
|
||||
xxs: 'w-3 h-3',
|
||||
xs: 'w-4 h-4',
|
||||
sm: 'w-4 h-4',
|
||||
md: 'w-5 h-5',
|
||||
lg: 'w-5 h-5',
|
||||
xl: 'w-5 h-5'
|
||||
})[this.size]
|
||||
},
|
||||
iconPadding () {
|
||||
return ({
|
||||
xxs: 'pl-3',
|
||||
xs: 'pl-3',
|
||||
sm: 'pl-3',
|
||||
md: 'pl-3',
|
||||
lg: 'pl-3',
|
||||
xl: 'pl-4'
|
||||
})[this.size]
|
||||
},
|
||||
selectClass () {
|
||||
return [
|
||||
this.baseClass,
|
||||
this.customClass,
|
||||
this.sizeClass,
|
||||
this.paddingClass
|
||||
].join(' ')
|
||||
},
|
||||
normalizedOptions () {
|
||||
return this.options.map(option => this.normalizeOption(option))
|
||||
},
|
||||
normalizedOptionsWithPlaceholder () {
|
||||
if (!this.placeholder) {
|
||||
return this.normalizedOptions
|
||||
}
|
||||
const { normalizedOptions } = this
|
||||
normalizedOptions.unshift({
|
||||
[this.valueAttribute]: null,
|
||||
[this.textAttribute]: this.placeholder
|
||||
})
|
||||
return normalizedOptions
|
||||
},
|
||||
normalizedValue () {
|
||||
const foundOption = this.normalizedOptionsWithPlaceholder.find(option => option.value === this.modelValue)
|
||||
if (!foundOption) {
|
||||
return null
|
||||
setup (props, { emit }) {
|
||||
const select = ref(null)
|
||||
|
||||
const onInput = (value) => {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
|
||||
return foundOption.value
|
||||
const guessOptionValue = (option) => {
|
||||
return get(option, props.valueAttribute, get(option, props.textAttribute))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
guessOptionValue (option) {
|
||||
return get(option, this.valueAttribute, get(option, this.textAttribute))
|
||||
},
|
||||
guessOptionText (option) {
|
||||
return get(option, this.textAttribute, get(option, this.valueAttribute))
|
||||
},
|
||||
normalizeOption (option) {
|
||||
|
||||
const guessOptionText = (option) => {
|
||||
return get(option, props.textAttribute, get(option, props.valueAttribute))
|
||||
}
|
||||
|
||||
const normalizeOption = (option) => {
|
||||
if (['string', 'number', 'boolean'].includes(typeof option)) {
|
||||
return {
|
||||
[this.valueAttribute]: option,
|
||||
[this.textAttribute]: option
|
||||
[props.valueAttribute]: option,
|
||||
[props.textAttribute]: option
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...option,
|
||||
[this.valueAttribute]: this.guessOptionValue(option),
|
||||
[this.textAttribute]: this.guessOptionText(option)
|
||||
[props.valueAttribute]: guessOptionValue(option),
|
||||
[props.textAttribute]: guessOptionText(option)
|
||||
}
|
||||
}
|
||||
|
||||
const normalizedOptions = computed(() => {
|
||||
return props.options.map(option => normalizeOption(option))
|
||||
})
|
||||
|
||||
const normalizedOptionsWithPlaceholder = computed(() => {
|
||||
if (!props.placeholder) {
|
||||
return normalizedOptions.value
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
[props.valueAttribute]: null,
|
||||
[props.textAttribute]: props.placeholder
|
||||
},
|
||||
updateValue (value) {
|
||||
this.$emit('update:modelValue', value)
|
||||
...normalizedOptions.value
|
||||
]
|
||||
})
|
||||
|
||||
const normalizedValue = computed(() => {
|
||||
const foundOption = normalizedOptionsWithPlaceholder.value.find(option => option.value === props.modelValue)
|
||||
if (!foundOption) {
|
||||
return null
|
||||
}
|
||||
|
||||
return foundOption.value
|
||||
})
|
||||
|
||||
const selectClass = computed(() => {
|
||||
return classNames(
|
||||
props.baseClass,
|
||||
$ui.select.size[props.size],
|
||||
$ui.select.spacing[props.size],
|
||||
$ui.select.appearance.default,
|
||||
!!props.icon && $ui.select.leading.spacing[props.size],
|
||||
$ui.select.trailing.spacing[props.size],
|
||||
props.customClass
|
||||
)
|
||||
})
|
||||
|
||||
const iconClass = computed(() => {
|
||||
return classNames(
|
||||
props.iconBaseClass,
|
||||
$ui.select.icon.size[props.size],
|
||||
!!props.icon && $ui.select.icon.leading.spacing[props.size]
|
||||
)
|
||||
})
|
||||
|
||||
const iconWrapperClass = $ui.select.icon.leading.base
|
||||
|
||||
return {
|
||||
select,
|
||||
onInput,
|
||||
guessOptionValue,
|
||||
guessOptionText,
|
||||
normalizeOption,
|
||||
normalizedOptions,
|
||||
normalizedOptionsWithPlaceholder,
|
||||
normalizedValue,
|
||||
selectClass,
|
||||
iconClass,
|
||||
iconWrapperClass
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
:disabled="disabled"
|
||||
:placeholder="placeholder"
|
||||
:autocomplete="autocomplete"
|
||||
:class="[baseClass, customClass, sizeClass, paddingClass, appearanceClass, resizeClass]"
|
||||
:class="textareaClass"
|
||||
@input="onInput($event.target.value)"
|
||||
@focus="$emit('focus', $event)"
|
||||
@blur="$emit('blur', $event)"
|
||||
@@ -20,6 +20,8 @@
|
||||
|
||||
<script>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { classNames } from '../../utils'
|
||||
import $ui from '#build/ui'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -63,7 +65,7 @@ export default {
|
||||
type: String,
|
||||
default: 'default',
|
||||
validator (value) {
|
||||
return ['default', 'none'].includes(value)
|
||||
return Object.keys($ui.textarea.appearance).includes(value)
|
||||
}
|
||||
},
|
||||
resize: {
|
||||
@@ -74,16 +76,16 @@ export default {
|
||||
type: String,
|
||||
default: 'md',
|
||||
validator (value) {
|
||||
return ['', 'xxs', 'xs', 'sm', 'md', 'lg', 'xl'].includes(value)
|
||||
return Object.keys($ui.textarea.size).includes(value)
|
||||
}
|
||||
},
|
||||
wrapperClass: {
|
||||
type: String,
|
||||
default: 'relative'
|
||||
default: $ui.textarea.wrapper
|
||||
},
|
||||
baseClass: {
|
||||
type: String,
|
||||
default: 'block w-full u-bg-white u-text-gray-700 disabled:cursor-not-allowed disabled:u-bg-gray-50 focus:outline-none'
|
||||
default: $ui.textarea.base
|
||||
},
|
||||
customClass: {
|
||||
type: String,
|
||||
@@ -127,40 +129,21 @@ export default {
|
||||
}, 100)
|
||||
})
|
||||
|
||||
const sizeClass = computed(() => ({
|
||||
xxs: 'text-xs',
|
||||
xs: 'text-xs',
|
||||
sm: 'text-sm leading-4',
|
||||
md: 'text-sm',
|
||||
lg: 'text-base',
|
||||
xl: 'text-base'
|
||||
})[props.size])
|
||||
|
||||
const paddingClass = computed(() => ({
|
||||
xxs: 'px-1 py-0.5',
|
||||
xs: 'px-2.5 py-1.5',
|
||||
sm: 'px-3 py-2',
|
||||
md: 'px-4 py-2',
|
||||
lg: 'px-4 py-2',
|
||||
xl: 'px-6 py-3'
|
||||
})[props.size])
|
||||
|
||||
const appearanceClass = computed(() => ({
|
||||
default: 'focus:ring-1 focus:ring-primary-500 focus:border-primary-500 border u-border-gray-300 rounded-md shadow-sm',
|
||||
none: 'border-0 bg-transparent focus:ring-0 focus:shadow-none'
|
||||
})[props.appearance])
|
||||
|
||||
const resizeClass = computed(() => {
|
||||
return props.resize ? '' : 'resize-none'
|
||||
const textareaClass = computed(() => {
|
||||
return classNames(
|
||||
props.baseClass,
|
||||
$ui.textarea.size[props.size],
|
||||
$ui.textarea.spacing[props.size],
|
||||
$ui.textarea.appearance[props.appearance],
|
||||
!props.resize && 'resize-none',
|
||||
props.customClass
|
||||
)
|
||||
})
|
||||
|
||||
return {
|
||||
textarea,
|
||||
onInput,
|
||||
sizeClass,
|
||||
paddingClass,
|
||||
appearanceClass,
|
||||
resizeClass
|
||||
textareaClass
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
src/index.ts
59
src/index.ts
@@ -1,6 +1,7 @@
|
||||
import { resolve } from 'pathe'
|
||||
import { defineNuxtModule, installModule, addComponentsDir } from '@nuxt/kit'
|
||||
import { defineNuxtModule, installModule, addComponentsDir, addTemplate } from '@nuxt/kit'
|
||||
import { colors } from '@unocss/preset-uno'
|
||||
import defu from 'defu'
|
||||
import type { UnocssNuxtOptions } from '@unocss/nuxt'
|
||||
|
||||
export interface UiColorsOptions {
|
||||
@@ -17,8 +18,11 @@ export interface UiColorsOptions {
|
||||
|
||||
export interface UiOptions {
|
||||
/**
|
||||
* Prefix of injected components.
|
||||
*
|
||||
* @default 'tailwindui'
|
||||
*/
|
||||
preset?: string | object
|
||||
|
||||
/**
|
||||
* @default 'u'
|
||||
*/
|
||||
prefix?: string
|
||||
@@ -28,10 +32,8 @@ export interface UiOptions {
|
||||
unocss?: UnocssNuxtOptions
|
||||
}
|
||||
|
||||
export default defineNuxtModule<UiOptions>({
|
||||
name: '@nuxthq/ui',
|
||||
configKey: 'ui',
|
||||
defaults: {
|
||||
const defaults = {
|
||||
preset: 'tailwindui',
|
||||
prefix: 'u',
|
||||
colors: {
|
||||
primary: 'indigo',
|
||||
@@ -43,9 +45,14 @@ export default defineNuxtModule<UiOptions>({
|
||||
variants: [],
|
||||
theme: {}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default defineNuxtModule<UiOptions>({
|
||||
name: '@nuxthq/ui',
|
||||
configKey: 'ui',
|
||||
defaults,
|
||||
async setup (_options, nuxt) {
|
||||
const { prefix, colors: { primary = 'indigo', gray = 'zinc' } = {} } = _options
|
||||
const { preset, prefix, colors: { primary = 'indigo', gray = 'zinc' } = {} } = _options
|
||||
const { shortcuts = [], rules = [], variants = [], theme = {} } = _options.unocss || {}
|
||||
|
||||
const options: UnocssNuxtOptions = {
|
||||
@@ -134,7 +141,18 @@ export default defineNuxtModule<UiOptions>({
|
||||
}],
|
||||
...rules
|
||||
],
|
||||
variants,
|
||||
variants: [
|
||||
// disabled:
|
||||
(matcher) => {
|
||||
if (!matcher.startsWith('disabled:')) { return matcher }
|
||||
return {
|
||||
// slice `disabled:` prefix and passed to the next variants and rules
|
||||
matcher: matcher.slice(9),
|
||||
selector: s => `${s}:disabled`
|
||||
}
|
||||
},
|
||||
...variants
|
||||
],
|
||||
layers: {
|
||||
icons: 0,
|
||||
default: 1,
|
||||
@@ -144,6 +162,24 @@ export default defineNuxtModule<UiOptions>({
|
||||
|
||||
await installModule(nuxt, { src: '@unocss/nuxt', options })
|
||||
|
||||
let ui: object = {}
|
||||
try {
|
||||
if (typeof preset === 'object') {
|
||||
ui = await import(resolve(__dirname, `./presets/${defaults.preset}`))
|
||||
|
||||
ui = defu(preset, ui)
|
||||
} else {
|
||||
ui = await import(resolve(__dirname, `./presets/${preset}`))
|
||||
}
|
||||
} catch (e) {
|
||||
ui = await import(resolve(__dirname, `./presets/${defaults.preset}`))
|
||||
}
|
||||
|
||||
addTemplate({
|
||||
filename: 'ui.mjs',
|
||||
getContents: () => `/* @unocss-include */ export default ${JSON.stringify(ui)}`
|
||||
})
|
||||
|
||||
addComponentsDir({
|
||||
path: resolve(__dirname, './components/elements'),
|
||||
prefix,
|
||||
@@ -183,4 +219,7 @@ declare module '@nuxt/schema' {
|
||||
interface NuxtConfig {
|
||||
ui?: UiOptions
|
||||
}
|
||||
interface NuxtOptions {
|
||||
ui?: UiOptions
|
||||
}
|
||||
}
|
||||
|
||||
96
src/presets/tailwindui.ts
Normal file
96
src/presets/tailwindui.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
const input = {
|
||||
wrapper: 'relative',
|
||||
base: 'block w-full u-bg-white u-text-gray-700 disabled:cursor-not-allowed disabled:opacity-75 focus:outline-none',
|
||||
size: {
|
||||
xxs: 'text-xs',
|
||||
xs: 'text-xs',
|
||||
sm: 'text-sm leading-4',
|
||||
md: 'text-sm',
|
||||
lg: 'text-base',
|
||||
xl: 'text-base'
|
||||
},
|
||||
spacing: {
|
||||
xxs: 'px-1 py-0.5',
|
||||
xs: 'px-2.5 py-1.5',
|
||||
sm: 'px-3 py-2',
|
||||
md: 'px-4 py-2',
|
||||
lg: 'px-4 py-2',
|
||||
xl: 'px-6 py-3'
|
||||
},
|
||||
leading: {
|
||||
spacing: {
|
||||
xxs: 'pl-7',
|
||||
xs: 'pl-7',
|
||||
sm: 'pl-10',
|
||||
md: 'pl-10',
|
||||
lg: 'pl-10',
|
||||
xl: 'pl-10'
|
||||
}
|
||||
},
|
||||
trailing: {
|
||||
spacing: {
|
||||
xxs: 'pr-7',
|
||||
xs: 'pr-7',
|
||||
sm: 'pr-10',
|
||||
md: 'pr-10',
|
||||
lg: 'pr-10',
|
||||
xl: 'pr-10'
|
||||
}
|
||||
},
|
||||
appearance: {
|
||||
default: 'focus:ring-1 focus:ring-primary-500 focus:border-primary-500 border u-border-gray-300 rounded-md shadow-sm',
|
||||
none: 'border-0 bg-transparent focus:ring-0 focus:shadow-none'
|
||||
},
|
||||
icon: {
|
||||
base: 'u-text-gray-400',
|
||||
size: {
|
||||
xxs: 'h-3 w-3',
|
||||
xs: 'h-4 w-4',
|
||||
sm: 'h-5 w-5',
|
||||
md: 'h-5 w-5',
|
||||
lg: 'h-5 w-5',
|
||||
xl: 'h-5 w-5'
|
||||
},
|
||||
leading: {
|
||||
wrapper: 'absolute inset-y-0 left-0 flex items-center pointer-events-none',
|
||||
spacing: {
|
||||
xxs: 'ml-2',
|
||||
xs: 'ml-2',
|
||||
sm: 'ml-3',
|
||||
md: 'ml-3',
|
||||
lg: 'ml-3',
|
||||
xl: 'ml-3'
|
||||
}
|
||||
},
|
||||
trailing: {
|
||||
wrapper: 'absolute inset-y-0 right-0 flex items-center pointer-events-none',
|
||||
spacing: {
|
||||
xxs: 'mr-2',
|
||||
xs: 'mr-2',
|
||||
sm: 'mr-3',
|
||||
md: 'mr-3',
|
||||
lg: 'mr-3',
|
||||
xl: 'mr-3'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const textarea = {
|
||||
...input
|
||||
}
|
||||
|
||||
const select = {
|
||||
...input
|
||||
}
|
||||
|
||||
const button = {
|
||||
base: ''
|
||||
}
|
||||
|
||||
export default {
|
||||
input,
|
||||
textarea,
|
||||
select,
|
||||
button
|
||||
}
|
||||
Reference in New Issue
Block a user