chore(components): update

This commit is contained in:
Benjamin Canac
2021-12-10 16:07:36 +01:00
parent 3e757d267f
commit 550fb9dc1d
7 changed files with 314 additions and 266 deletions

View File

@@ -1,26 +1,25 @@
<template> <template>
<div class="relative flex" :class="{ 'items-start': label, 'items-center': !label }"> <div :class="wrapperClass">
<div class="flex items-center h-5"> <div class="flex items-center h-5">
<input <input
:id="name" :id="name"
:checked="isChecked" v-model="isChecked"
:name="name" :name="name"
:required="required" :required="required"
:value="value" :value="value"
:disabled="disabled" :disabled="disabled"
type="checkbox" type="checkbox"
:class="inputClass" :class="inputClass"
@focus="focused = true" @focus="$emit('focus', $event)"
@blur="focused = false" @blur="$emit('blur', $event)"
@change="onChange"
> >
</div> </div>
<div v-if="label" class="ml-3 text-sm"> <div v-if="label" class="ml-3 text-sm">
<label :for="name" class="font-medium u-text-gray-700"> <label :for="name" :class="labelClass">
{{ label }} {{ label }}
<span v-if="required" class="text-red-400">*</span> <span v-if="required" :class="requiredClass">*</span>
</label> </label>
<p v-if="help" class="u-text-gray-500"> <p v-if="help" :class="helpClass">
{{ help }} {{ help }}
</p> </p>
</div> </div>
@@ -28,28 +27,24 @@
</template> </template>
<script> <script>
import { computed } from 'vue'
import { classNames } from '../../utils'
import $ui from '#build/ui'
export default { export default {
model: {
prop: 'checked',
event: 'change'
},
props: { props: {
value: { value: {
type: [String, Number, Boolean], type: [String, Number, Boolean],
default: null default: null
}, },
id: { modelValue: {
type: String, type: [String, Number, Boolean, Array],
default: null default: null
}, },
name: { name: {
type: String, type: String,
default: null default: null
}, },
checked: {
type: [Array, Boolean],
default: null
},
disabled: { disabled: {
type: Boolean, type: Boolean,
default: false default: false
@@ -66,39 +61,52 @@ export default {
type: Boolean, type: Boolean,
default: false default: false
}, },
wrapperClass: {
type: String,
default: () => $ui.checkbox.wrapper
},
baseClass: { baseClass: {
type: String, type: String,
default: 'h-4 w-4 text-primary-600 focus:ring-1 focus:ring-primary-500 u-border-gray-300 focus:border-primary-500 dark:focus:border-primary-500 u-bg-white dark:checked:bg-primary-600 dark:checked:border-primary-600 focus:ring-offset-0 disabled:opacity-50 disabled:cursor-not-allowed rounded' default: () => $ui.checkbox.base
},
labelClass: {
type: String,
default: () => $ui.checkbox.label
},
requiredClass: {
type: String,
default: () => $ui.checkbox.required
},
helpClass: {
type: String,
default: () => $ui.checkbox.help
}, },
customClass: { customClass: {
type: String, type: String,
default: null default: null
} }
}, },
data () { emits: ['update:modelValue', 'focus', 'blur'],
setup (props, { emit }) {
const isChecked = computed({
get () {
return props.modelValue
},
set (value) {
emit('update:modelValue', value)
}
})
const inputClass = computed(() => {
return classNames(
props.baseClass,
props.customClass
)
})
return { return {
focused: false isChecked,
} inputClass
},
computed: {
isChecked () {
return Array.isArray(this.checked) ? this.checked.includes(this.value) : this.checked
},
inputClass () {
return [
this.baseClass,
this.customClass
].join(' ')
}
},
methods: {
onChange () {
// We check if we have validation error and clean it as the user as typed a new value
if (this.newValidation) { this.newValidation = null }
if (!Array.isArray(this.checked)) { return this.$emit('change', !this.checked) }
if (this.checked.includes(this.value)) { this.$emit('change', this.checked.filter(c => c !== this.value)) } else { this.$emit('change', this.checked.concat(this.value)) }
} }
} }
} }

View File

@@ -15,6 +15,9 @@
<slot name="hint">{{ hint }}</slot> <slot name="hint">{{ hint }}</slot>
</span> </span>
</div> </div>
<p v-if="description" :class="descriptionClass">
{{ description }}
</p>
</slot> </slot>
<div :class="!!label && containerClass"> <div :class="!!label && containerClass">
<slot /> <slot />
@@ -32,12 +35,16 @@ export default {
props: { props: {
name: { name: {
type: String, type: String,
required: true default: null
}, },
label: { label: {
type: String, type: String,
default: null default: null
}, },
description: {
type: String,
default: null
},
required: { required: {
type: Boolean, type: Boolean,
default: false default: false
@@ -52,23 +59,27 @@ export default {
}, },
containerClass: { containerClass: {
type: String, type: String,
default: () => $ui.inputGroup.container default: () => $ui.formGroup.container
}, },
labelClass: { labelClass: {
type: String, type: String,
default: () => $ui.inputGroup.label default: () => $ui.formGroup.label
},
descriptionClass: {
type: String,
default: () => $ui.formGroup.description
}, },
requiredClass: { requiredClass: {
type: String, type: String,
default: () => $ui.inputGroup.required default: () => $ui.formGroup.required
}, },
hintClass: { hintClass: {
type: String, type: String,
default: () => $ui.inputGroup.hint default: () => $ui.formGroup.hint
}, },
helpClass: { helpClass: {
type: String, type: String,
default: () => $ui.inputGroup.help default: () => $ui.formGroup.help
} }
} }
} }

View File

@@ -1,36 +1,43 @@
<template> <template>
<label :for="`${name}-${value}`" class="relative flex cursor-pointer"> <div :class="wrapperClass">
<input <div class="flex items-center h-5">
:id="`${name}-${value}`" <input
:checked="checked" :id="`${name}-${value}`"
:name="name" v-model="isChecked"
:required="required" :name="name"
:value="value" :required="required"
:disabled="disabled" :value="value"
type="radio" :disabled="disabled"
:class="inputClass" type="radio"
@focus="focused = true" :class="radioClass"
@blur="focused = false" @focus="$emit('focus', $event)"
@change="onChange" @blur="$emit('blur', $event)"
> >
<div v-if="label" class="flex flex-col ml-3">
<span class="block text-sm font-medium u-text-gray-900">
{{ label }}
<span v-if="required" class="text-red-400">*</span>
</span>
<span v-if="help" class="block text-sm u-text-gray-500">{{ help }}</span>
</div> </div>
</label> <div v-if="label" class="ml-3 text-sm">
<label :for="`${name}-${value}`" :class="labelClass">
{{ label }}
<span v-if="required" :class="requiredClass">*</span>
</label>
<p v-if="help" :class="helpClass">
{{ help }}
</p>
</div>
</div>
</template> </template>
<script> <script>
import { computed } from 'vue'
import { classNames } from '../../utils'
import $ui from '#build/ui'
export default { export default {
model: {
prop: 'checked',
event: 'change'
},
props: { props: {
value: { value: {
type: [String, Number, Boolean],
default: null
},
modelValue: {
type: [String, Number, Boolean, Object], type: [String, Number, Boolean, Object],
default: null default: null
}, },
@@ -38,10 +45,6 @@ export default {
type: String, type: String,
default: null default: null
}, },
checked: {
type: Boolean,
default: null
},
disabled: { disabled: {
type: Boolean, type: Boolean,
default: false default: false
@@ -58,31 +61,52 @@ export default {
type: Boolean, type: Boolean,
default: false default: false
}, },
wrapperClass: {
type: String,
default: () => $ui.radio.wrapper
},
baseClass: { baseClass: {
type: String, type: String,
default: 'h-4 w-4 mt-0.5 text-primary-600 checked:border-primary-600 dark:checked:border-primary-600 focus:ring-1 focus:ring-primary-500 u-border-gray-300 u-bg-white dark:checked:bg-primary-600 focus:ring-offset-white dark:focus:ring-offset-gray-900 disabled:opacity-50 disabled:cursor-not-allowed' default: () => $ui.radio.base
},
labelClass: {
type: String,
default: () => $ui.radio.label
},
requiredClass: {
type: String,
default: () => $ui.radio.required
},
helpClass: {
type: String,
default: () => $ui.radio.help
}, },
customClass: { customClass: {
type: String, type: String,
default: null default: null
} }
}, },
data () { emits: ['update:modelValue', 'focus', 'blur'],
setup (props, { emit }) {
const isChecked = computed({
get () {
return props.modelValue
},
set (value) {
emit('update:modelValue', value)
}
})
const radioClass = computed(() => {
return classNames(
props.baseClass,
props.customClass
)
})
return { return {
focused: false isChecked,
} radioClass
},
computed: {
inputClass () {
return [
this.baseClass,
this.customClass
].join(' ')
}
},
methods: {
onChange () {
this.$emit('change', this.value)
} }
} }
} }

View File

@@ -1,72 +0,0 @@
<template>
<fieldset :id="name">
<legend v-if="label" class="sr-only">
{{ label }}
</legend>
<div :class="wrapperClass">
<Radio
v-for="(option, index) in options"
:key="index"
:checked="option.value === value"
:disabled="disabled"
:name="name"
v-bind="option"
:class="inputClass"
@change="onChange"
/>
</div>
</fieldset>
</template>
<script>
import Radio from './Radio'
export default {
components: {
Radio
},
model: {
event: 'change'
},
props: {
name: {
type: String,
required: true
},
label: {
type: String,
required: true
},
value: {
type: [String, Number, Boolean, Object],
default: null
},
options: {
type: [Array],
required: true
},
required: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
wrapperClass: {
type: String,
default: null
},
inputClass: {
type: String,
default: null
}
},
methods: {
onChange (value) {
this.$emit('change', value)
}
}
}
</script>

View File

@@ -1,7 +1,7 @@
<template> <template>
<component <component
:is="$attrs.onSubmit ? 'form': 'div'" :is="$attrs.onSubmit ? 'form': 'div'"
:class="[padded && rounded && 'rounded-md', !padded && rounded && 'sm:rounded-md', wrapperClass, ringClass, shadowClass, backgroundClass]" :class="cardClass"
v-bind="$attrs" v-bind="$attrs"
> >
<div <div
@@ -23,6 +23,10 @@
</template> </template>
<script> <script>
import { computed } from 'vue'
import { classNames } from '../../utils/'
import $ui from '#build/ui'
export default { export default {
props: { props: {
padded: { padded: {
@@ -33,25 +37,29 @@ export default {
type: Boolean, type: Boolean,
default: true default: true
}, },
wrapperClass: { baseClass: {
type: String, type: String,
default: 'overflow-hidden' default: () => $ui.card.base
}, },
backgroundClass: { backgroundClass: {
type: String, type: String,
default: 'u-bg-white' default: () => $ui.card.background
},
borderColorClass: {
type: String,
default: () => $ui.card.border
}, },
shadowClass: { shadowClass: {
type: String, type: String,
default: '' default: () => $ui.card.shadow
}, },
ringClass: { ringClass: {
type: String, type: String,
default: 'ring-1 u-ring-gray-200' default: () => $ui.card.ring
}, },
bodyClass: { bodyClass: {
type: String, type: String,
default: 'px-4 py-5 sm:p-6' default: () => $ui.card.body
}, },
bodyBackgroundClass: { bodyBackgroundClass: {
type: String, type: String,
@@ -59,7 +67,7 @@ export default {
}, },
headerClass: { headerClass: {
type: String, type: String,
default: 'px-4 py-5 sm:px-6' default: () => $ui.card.header
}, },
headerBackgroundClass: { headerBackgroundClass: {
type: String, type: String,
@@ -67,15 +75,32 @@ export default {
}, },
footerClass: { footerClass: {
type: String, type: String,
default: 'px-4 py-4 sm:px-6' default: () => $ui.card.footer
}, },
footerBackgroundClass: { footerBackgroundClass: {
type: String, type: String,
default: null default: null
}, },
borderColorClass: { customClass: {
type: String, type: String,
default: 'u-border-gray-200' default: null
}
},
setup (props) {
const cardClass = computed(() => {
return classNames(
props.baseClass,
props.padded && props.rounded && 'rounded-md',
!props.padded && props.rounded && 'sm:rounded-md',
props.ringClass,
props.shadowClass,
props.backgroundClass,
props.customClass
)
})
return {
cardClass
} }
} }
} }

View File

@@ -1,10 +1,14 @@
<template> <template>
<div class="mx-auto sm:px-6 lg:px-8" :class="{ 'px-4': padded, 'max-w-7xl': constrained }"> <div :class="containerClass">
<slot /> <slot />
</div> </div>
</template> </template>
<script> <script>
import { computed } from 'vue'
import { classNames } from '../../utils/'
import $ui from '#build/ui'
export default { export default {
props: { props: {
padded: { padded: {
@@ -14,6 +18,23 @@ export default {
constrained: { constrained: {
type: Boolean, type: Boolean,
default: true default: true
},
constrainedClass: {
type: String,
default: () => $ui.container.constrained
}
},
setup (props) {
const containerClass = computed(() => {
return classNames(
'mx-auto sm:px-6 lg:px-8',
props.padded && 'px-4',
props.constrained && props.constrainedClass
)
})
return {
containerClass
} }
} }
} }

View File

@@ -19,6 +19,91 @@ const colors = [
'red' 'red'
] ]
const button = {
base: 'font-medium focus:outline-none disabled:cursor-not-allowed disabled:opacity-75',
size: {
xxs: 'text-xs',
xs: 'text-xs',
sm: 'text-sm leading-4',
md: 'text-sm',
lg: 'text-base',
xl: 'text-base'
},
spacing: {
xxs: 'px-2 py-1',
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'
},
square: {
xxs: 'p-1',
xs: 'p-1.5',
sm: 'p-2',
md: 'p-2',
lg: 'p-2',
xl: 'p-3'
},
variant: {
...colors.reduce((acc: any, color) => {
acc[color] = `shadow-sm border border-transparent text-white bg-${color}-600 hover:bg-${color}-700 disabled:bg-${color}-600 focus:ring-2 focus:ring-${color}-200`
return acc
}, {}),
primary: 'shadow-sm border border-transparent text-white bg-primary-600 hover:bg-primary-700 disabled:bg-primary-600 focus:ring-2 focus:ring-primary-200',
secondary: 'border border-transparent text-primary-700 bg-primary-100 hover:bg-primary-200 disabled:bg-primary-100 focus:ring-2 focus:ring-primary-500',
white: 'shadow-sm border u-border-gray-300 u-text-gray-700 u-bg-white hover:u-bg-gray-50 disabled:u-bg-white focus:ring-1 focus:ring-primary-500 focus:border-primary-500 dark:focus:border-primary-500',
'white-hover': 'border border-transparent u-text-gray-500 hover:u-text-gray-700 focus:u-text-gray-700 bg-transparent hover:bg-gray-100 focus:bg-gray-100 dark:hover:bg-gray-900 dark:focus:bg-gray-900 disabled:u-text-gray-500',
gray: 'shadow-sm border u-border-gray-300 u-text-gray-500 hover:u-text-gray-700 focus:u-text-gray-700 bg-gray-50 dark:bg-gray-800 disabled:u-text-gray-500 focus:ring-primary-500 focus:border-primary-500 dark:focus:border-primary-500',
'gray-hover': 'border border-transparent u-text-gray-500 hover:u-text-gray-700 focus:u-text-gray-700 bg-transparent hover:bg-gray-100 focus:bg-gray-100 dark:hover:bg-gray-800 dark:focus:bg-gray-800 disabled:u-text-gray-500',
black: 'border border-transparent u-text-white u-bg-gray-800 hover:u-bg-gray-900 focus:u-bg-gray-900',
'black-hover': 'border border-transparent u-text-gray-500 hover:u-text-gray-900 focus:u-text-gray-700 bg-transparent hover:bg-white dark:hover:bg-black focus:bg-white dark:focus:bg-black',
transparent: 'border border-transparent u-text-gray-500 hover:u-text-gray-700 focus:u-text-gray-700 disabled:hover:u-text-gray-500',
link: 'border border-transparent text-primary-500 hover:text-primary-700 focus:text-primary-700'
},
icon: {
base: 'flex-shrink-0',
loading: 'heroicons-outline:refresh',
size: {
xxs: 'h-3.5 w-3.5',
xs: 'h-4 w-4',
sm: 'h-4 w-4',
md: 'h-5 w-5',
lg: 'h-5 w-5',
xl: 'h-5 w-5'
},
leading: {
spacing: {
xxs: '-ml-0.5 mr-1',
xs: '-ml-0.5 mr-1.5',
sm: '-ml-0.5 mr-2',
md: '-ml-1 mr-2',
lg: '-ml-1 mr-3',
xl: '-ml-1 mr-3'
}
},
trailing: {
spacing: {
xxs: 'ml-1 -mr-0.5',
xs: 'ml-1.5 -mr-0.5',
sm: 'ml-2 -mr-0.5',
md: 'ml-2 -mr-1',
lg: 'ml-3 -mr-1',
xl: 'ml-3 -mr-1'
}
}
}
}
const formGroup = {
label: 'block text-sm font-medium u-text-gray-700',
container: 'mt-1 relative',
required: 'text-red-400',
description: 'text-sm leading-5 u-text-gray-500',
hint: 'text-sm leading-5 u-text-gray-500',
help: 'mt-2 text-sm u-text-gray-500'
}
const input = { const input = {
wrapper: 'relative', wrapper: 'relative',
base: 'block w-full u-bg-white u-text-gray-700 disabled:cursor-not-allowed disabled:opacity-75 focus:outline-none', base: 'block w-full u-bg-white u-text-gray-700 disabled:cursor-not-allowed disabled:opacity-75 focus:outline-none',
@@ -98,18 +183,6 @@ const input = {
} }
} }
const inputGroup = {
label: 'block text-sm font-medium u-text-gray-700',
container: 'mt-1 relative',
required: 'text-red-400',
hint: 'text-sm leading-5 u-text-gray-500',
help: 'mt-2 text-sm u-text-gray-500'
}
const toggleGroup = {
...inputGroup
}
const textarea = { const textarea = {
...input ...input
} }
@@ -118,87 +191,45 @@ const select = {
...input ...input
} }
const button = { const checkbox = {
base: 'font-medium focus:outline-none disabled:cursor-not-allowed disabled:opacity-75', wrapper: 'relative flex items-start',
size: { base: 'focus:ring-primary-500 h-4 w-4 text-primary-600 u-border-gray-300 rounded',
xxs: 'text-xs', label: 'font-medium u-text-gray-700',
xs: 'text-xs', required: 'text-red-400',
sm: 'text-sm leading-4', help: 'u-text-gray-500'
md: 'text-sm', }
lg: 'text-base',
xl: 'text-base' const radio = {
}, wrapper: 'relative flex items-start',
spacing: { base: 'focus:ring-primary-500 h-4 w-4 text-primary-600 u-border-gray-300',
xxs: 'px-2 py-1', label: 'font-medium u-text-gray-700',
xs: 'px-2.5 py-1.5', required: 'text-red-400',
sm: 'px-3 py-2', help: 'u-text-gray-500'
md: 'px-4 py-2', }
lg: 'px-4 py-2',
xl: 'px-6 py-3' const card = {
}, base: 'overflow-hidden',
square: { background: 'u-bg-white',
xxs: 'p-1', border: 'u-border-gray-200',
xs: 'p-1.5', ring: 'ring-1 u-ring-gray-200',
sm: 'p-2', shadow: '',
md: 'p-2', body: 'px-4 py-5 sm:p-6',
lg: 'p-2', header: 'px-4 py-5 sm:px-6',
xl: 'p-3' footer: 'px-4 py-4 sm:px-6'
}, }
variant: {
...colors.reduce((acc: any, color) => { const container = {
acc[color] = `shadow-sm border border-transparent text-white bg-${color}-600 hover:bg-${color}-700 disabled:bg-${color}-600 focus:ring-2 focus:ring-${color}-200` constrained: 'max-w-7xl'
return acc
}, {}),
primary: 'shadow-sm border border-transparent text-white bg-primary-600 hover:bg-primary-700 disabled:bg-primary-600 focus:ring-2 focus:ring-primary-200',
secondary: 'border border-transparent text-primary-700 bg-primary-100 hover:bg-primary-200 disabled:bg-primary-100 focus:ring-2 focus:ring-primary-500',
white: 'shadow-sm border u-border-gray-300 u-text-gray-700 u-bg-white hover:u-bg-gray-50 disabled:u-bg-white focus:ring-1 focus:ring-primary-500 focus:border-primary-500 dark:focus:border-primary-500',
'white-hover': 'border border-transparent u-text-gray-500 hover:u-text-gray-700 focus:u-text-gray-700 bg-transparent hover:bg-gray-100 focus:bg-gray-100 dark:hover:bg-gray-900 dark:focus:bg-gray-900 disabled:u-text-gray-500',
gray: 'shadow-sm border u-border-gray-300 u-text-gray-500 hover:u-text-gray-700 focus:u-text-gray-700 bg-gray-50 dark:bg-gray-800 disabled:u-text-gray-500 focus:ring-primary-500 focus:border-primary-500 dark:focus:border-primary-500',
'gray-hover': 'border border-transparent u-text-gray-500 hover:u-text-gray-700 focus:u-text-gray-700 bg-transparent hover:bg-gray-100 focus:bg-gray-100 dark:hover:bg-gray-800 dark:focus:bg-gray-800 disabled:u-text-gray-500',
black: 'border border-transparent u-text-white u-bg-gray-800 hover:u-bg-gray-900 focus:u-bg-gray-900',
'black-hover': 'border border-transparent u-text-gray-500 hover:u-text-gray-900 focus:u-text-gray-700 bg-transparent hover:bg-white dark:hover:bg-black focus:bg-white dark:focus:bg-black',
transparent: 'border border-transparent u-text-gray-500 hover:u-text-gray-700 focus:u-text-gray-700 disabled:hover:u-text-gray-500',
link: 'border border-transparent text-primary-500 hover:text-primary-700 focus:text-primary-700'
},
icon: {
base: 'flex-shrink-0',
loading: 'heroicons-outline:refresh',
size: {
xxs: 'h-3.5 w-3.5',
xs: 'h-4 w-4',
sm: 'h-4 w-4',
md: 'h-5 w-5',
lg: 'h-5 w-5',
xl: 'h-5 w-5'
},
leading: {
spacing: {
xxs: '-ml-0.5 mr-1',
xs: '-ml-0.5 mr-1.5',
sm: '-ml-0.5 mr-2',
md: '-ml-1 mr-2',
lg: '-ml-1 mr-3',
xl: '-ml-1 mr-3'
}
},
trailing: {
spacing: {
xxs: 'ml-1 -mr-0.5',
xs: 'ml-1.5 -mr-0.5',
sm: 'ml-2 -mr-0.5',
md: 'ml-2 -mr-1',
lg: 'ml-3 -mr-1',
xl: 'ml-3 -mr-1'
}
}
}
} }
export default { export default {
button,
formGroup,
input, input,
inputGroup,
toggleGroup,
textarea, textarea,
select, select,
button checkbox,
radio,
card,
container
} }