feat(Radio/Checkbox/Toggle)!: handle color prop for form elements (#323)

Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
Haytham A. Salama
2023-06-20 18:35:53 +03:00
committed by Benjamin Canac
parent 97a1c86433
commit ffb312d34d
16 changed files with 207 additions and 45 deletions

View File

@@ -7,7 +7,7 @@
v-if="prop.type === 'boolean'" v-if="prop.type === 'boolean'"
v-model="componentProps[prop.name]" v-model="componentProps[prop.name]"
:name="`prop-${prop.name}`" :name="`prop-${prop.name}`"
variant="none" tabindex="-1"
:ui="{ wrapper: 'relative flex items-start justify-center' }" :ui="{ wrapper: 'relative flex items-start justify-center' }"
/> />
<USelectMenu <USelectMenu
@@ -18,6 +18,7 @@
variant="none" variant="none"
:ui="{ width: 'w-32 !-mt-px', rounded: 'rounded-b-md', wrapper: 'relative inline-flex' }" :ui="{ width: 'w-32 !-mt-px', rounded: 'rounded-b-md', wrapper: 'relative inline-flex' }"
class="!py-0" class="!py-0"
tabindex="-1"
:popper="{ strategy: 'fixed', placement: 'bottom-start' }" :popper="{ strategy: 'fixed', placement: 'bottom-start' }"
/> />
<UInput <UInput
@@ -28,6 +29,7 @@
variant="none" variant="none"
autocomplete="off" autocomplete="off"
class="!py-0" class="!py-0"
tabindex="-1"
@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>

View File

@@ -1,5 +1,5 @@
<script setup> <script setup>
const selected = ref(false) const selected = ref(true)
</script> </script>
<template> <template>

View File

@@ -33,7 +33,7 @@ Likewise, you can't define a `primary` color in your `tailwind.config.ts` as it
We'd advise you to use those colors in your components and pages, e.g. `text-primary-500 dark:text-primary-400`, `bg-gray-100 dark:bg-gray-900`, etc. so your app automatically adapts when changing your `app.config.ts`. We'd advise you to use those colors in your components and pages, e.g. `text-primary-500 dark:text-primary-400`, `bg-gray-100 dark:bg-gray-900`, etc. so your app automatically adapts when changing your `app.config.ts`.
:: ::
Components having a `color` prop like [Avatar](/elements/avatar#chip), [Badge](/elements/badge#style), [Button](/elements/button#style), [Input](/elements/input#style) (inherited in [Select](/forms/select) and [SelectMenu](/forms/select-menu)), [Range](/forms/range) and [Notification](/overlays/notification#timeout) will use the `primary` color by default but will handle all the colors defined in your `tailwind.config.ts` or the default Tailwind CSS colors. Components having a `color` prop like [Avatar](/elements/avatar#chip), [Badge](/elements/badge#style), [Button](/elements/button#style), [Input](/elements/input#style) (inherited in [Select](/forms/select) and [SelectMenu](/forms/select-menu)), [Radio](/forms/radio), [Checkbox](/forms/checkbox), [Toggle](/forms/toggle), [Range](/forms/range) and [Notification](/overlays/notification#timeout) will use the `primary` color by default but will handle all the colors defined in your `tailwind.config.ts` or the default Tailwind CSS colors.
Variant classes of those components are defined with a syntax like `bg-{color}-500 dark:bg-{color}-400` so they can be used with any color. However, this means that Tailwind will not find those classes and therefore will not generate the corresponding CSS. Variant classes of those components are defined with a syntax like `bg-{color}-500 dark:bg-{color}-400` so they can be used with any color. However, this means that Tailwind will not find those classes and therefore will not generate the corresponding CSS.

View File

@@ -14,7 +14,7 @@ Use a `v-model` to make the Checkbox reactive.
#code #code
```vue ```vue
<script setup> <script setup>
const selected = ref(false) const selected = ref(true)
</script> </script>
<template> <template>
@@ -36,6 +36,20 @@ props:
--- ---
:: ::
### Style
Use the `color` prop to change the style of the Checkbox.
::component-card
---
baseProps:
name: 'checkbox2'
label: 'Label'
props:
color: 'primary'
---
::
### Required ### Required
Use the `required` prop to display a red star next to the label. Use the `required` prop to display a red star next to the label.
@@ -43,7 +57,7 @@ Use the `required` prop to display a red star next to the label.
::component-card ::component-card
--- ---
baseProps: baseProps:
name: 'checkbox2' name: 'checkbox3'
props: props:
label: 'Label' label: 'Label'
required: true required: true
@@ -57,7 +71,7 @@ Use the `help` prop to display some text under the Checkbox.
::component-card ::component-card
--- ---
baseProps: baseProps:
name: 'checkbox3' name: 'checkbox4'
props: props:
label: 'Label' label: 'Label'
help: 'Please check this box' help: 'Please check this box'

View File

@@ -50,6 +50,20 @@ props:
--- ---
:: ::
### Style
Use the `color` prop to change the style of the Radio.
::component-card
---
baseProps:
name: 'radio2'
label: 'Label'
props:
color: 'primary'
---
::
### Required ### Required
Use the `required` prop to display a red star next to the label. Use the `required` prop to display a red star next to the label.
@@ -57,7 +71,7 @@ Use the `required` prop to display a red star next to the label.
::component-card ::component-card
--- ---
baseProps: baseProps:
name: 'radio2' name: 'radio3'
props: props:
label: 'Label' label: 'Label'
required: true required: true
@@ -71,7 +85,7 @@ Use the `help` prop to display some text under the Radio.
::component-card ::component-card
--- ---
baseProps: baseProps:
name: 'radio3' name: 'radio4'
props: props:
label: 'Label' label: 'Label'
help: 'Please choose one' help: 'Please choose one'
@@ -85,7 +99,7 @@ Use the `disabled` prop to disable the Radio.
::component-card ::component-card
--- ---
baseProps: baseProps:
name: 'radio4' name: 'radio5'
value: true value: true
props: props:
disabled: true disabled: true

View File

@@ -26,6 +26,17 @@ const selected = ref(false)
``` ```
:: ::
### Style
Use the `color` prop to change the style of the Toggle.
::component-card
---
props:
color: 'primary'
---
::
### Icon ### Icon
Use any icon from [Iconify](https://icones.js.org) by setting the `on-icon` and `off-icon` props by using this pattern: `i-{collection_name}-{icon_name}` or change it globally in `ui.toggle.default.onIcon` and `ui.toggle.default.offIcon`. Use any icon from [Iconify](https://icones.js.org) by setting the `on-icon` and `off-icon` props by using this pattern: `i-{collection_name}-{icon_name}` or change it globally in `ui.toggle.default.onIcon` and `ui.toggle.default.offIcon`.

View File

@@ -158,7 +158,7 @@ props:
--- ---
:: ::
### Color ### Style
Use the `color` prop to change the progress and icon color of the Notification. Use the `color` prop to change the progress and icon color of the Notification.

View File

@@ -103,6 +103,47 @@ const safelistByComponent = {
pattern: new RegExp(`ring-(${colorsAsRegex})-500`), pattern: new RegExp(`ring-(${colorsAsRegex})-500`),
variants: ['focus'] variants: ['focus']
}], }],
radio: (colorsAsRegex) => [{
pattern: new RegExp(`text-(${colorsAsRegex})-400`),
variants: ['dark']
}, {
pattern: new RegExp(`text-(${colorsAsRegex})-500`)
}, {
pattern: new RegExp(`ring-(${colorsAsRegex})-400`),
variants: ['dark:focus-visible']
}, {
pattern: new RegExp(`ring-(${colorsAsRegex})-500`),
variants: ['focus-visible']
}],
checkbox: (colorsAsRegex) => [{
pattern: new RegExp(`text-(${colorsAsRegex})-400`),
variants: ['dark']
}, {
pattern: new RegExp(`text-(${colorsAsRegex})-500`)
}, {
pattern: new RegExp(`ring-(${colorsAsRegex})-400`),
variants: ['dark:focus-visible']
}, {
pattern: new RegExp(`ring-(${colorsAsRegex})-500`),
variants: ['focus-visible']
}],
toggle: (colorsAsRegex) => [{
pattern: new RegExp(`bg-(${colorsAsRegex})-400`),
variants: ['dark']
}, {
pattern: new RegExp(`bg-(${colorsAsRegex})-500`)
}, {
pattern: new RegExp(`text-(${colorsAsRegex})-400`),
variants: ['dark']
}, {
pattern: new RegExp(`text-(${colorsAsRegex})-500`)
}, {
pattern: new RegExp(`ring-(${colorsAsRegex})-400`),
variants: ['dark:focus-visible']
}, {
pattern: new RegExp(`ring-(${colorsAsRegex})-500`),
variants: ['focus-visible']
}],
range: (colorsAsRegex) => [{ range: (colorsAsRegex) => [{
pattern: new RegExp(`bg-(${colorsAsRegex})-400`), pattern: new RegExp(`bg-(${colorsAsRegex})-400`),
variants: ['dark'] variants: ['dark']
@@ -144,7 +185,7 @@ const colorsAsRegex = (colors: string[]): string => colors.join('|')
export const excludeColors = (colors: object) => Object.keys(omit(colors, colorsToExclude)).map(color => kebabCase(color)) as string[] export const excludeColors = (colors: object) => Object.keys(omit(colors, colorsToExclude)).map(color => kebabCase(color)) as string[]
export const generateSafelist = (colors: string[]) => { export const generateSafelist = (colors: string[]) => {
const safelist = ['avatar', 'badge', 'button', 'input', 'range', 'notification'].flatMap(component => safelistByComponent[component](colorsAsRegex(colors))) const safelist = Object.keys(safelistByComponent).flatMap(component => safelistByComponent[component](colorsAsRegex(colors)))
return [ return [
...safelist, ...safelist,

View File

@@ -473,24 +473,40 @@ const selectMenu = {
const radio = { const radio = {
wrapper: 'relative flex items-start', wrapper: 'relative flex items-start',
base: 'h-4 w-4 text-primary-500 dark:text-primary-400 focus-visible:ring-2 focus-visible:ring-offset-2 bg-white dark:bg-gray-900 dark:checked:bg-current dark:checked:border-transparent focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900 border-gray-300 dark:border-gray-700 disabled:opacity-50 disabled:cursor-not-allowed focus:ring-0 focus:ring-transparent focus:ring-offset-transparent', base: 'h-4 w-4 dark:checked:bg-current dark:checked:border-transparent disabled:opacity-50 disabled:cursor-not-allowed focus:ring-0 focus:ring-transparent focus:ring-offset-transparent',
color: 'text-{color}-500 dark:text-{color}-400',
background: 'bg-white dark:bg-gray-900',
border: 'border border-gray-300 dark:border-gray-700',
ring: 'focus-visible:ring-2 focus-visible:ring-{color}-500 dark:focus-visible:ring-{color}-400 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900',
label: 'font-medium text-gray-700 dark:text-gray-200', label: 'font-medium text-gray-700 dark:text-gray-200',
required: 'text-red-500 dark:text-red-400', required: 'text-red-500 dark:text-red-400',
help: 'text-gray-500 dark:text-gray-400' help: 'text-gray-500 dark:text-gray-400',
default: {
color: 'primary'
}
} }
const checkbox = { const checkbox = {
wrapper: 'relative flex items-start', wrapper: 'relative flex items-start',
base: 'h-4 w-4 text-primary-500 dark:text-primary-400 focus-visible:ring-2 focus-visible:ring-offset-2 bg-white dark:bg-gray-900 dark:checked:bg-current dark:checked:border-transparent dark:indeterminate:bg-current dark:indeterminate:border-transparent focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900 border-gray-300 dark:border-gray-700 disabled:opacity-50 disabled:cursor-not-allowed focus:ring-0 focus:ring-transparent focus:ring-offset-transparent', base: 'h-4 w-4 dark:checked:bg-current dark:checked:border-transparent dark:indeterminate:bg-current dark:indeterminate:border-transparent disabled:opacity-50 disabled:cursor-not-allowed focus:ring-0 focus:ring-transparent focus:ring-offset-transparent',
rounded: 'rounded', rounded: 'rounded',
color: 'text-{color}-500 dark:text-{color}-400',
background: 'bg-white dark:bg-gray-900',
border: 'border border-gray-300 dark:border-gray-700',
ring: 'focus-visible:ring-2 focus-visible:ring-{color}-500 dark:focus-visible:ring-{color}-400 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900',
label: 'font-medium text-gray-700 dark:text-gray-200', label: 'font-medium text-gray-700 dark:text-gray-200',
required: 'text-red-500 dark:text-red-400', required: 'text-red-500 dark:text-red-400',
help: 'text-gray-500 dark:text-gray-400' help: 'text-gray-500 dark:text-gray-400',
default: {
color: 'primary'
}
} }
const toggle = { const toggle = {
base: 'relative inline-flex flex-shrink-0 h-5 w-9 border-2 border-transparent rounded-full cursor-pointer disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900', base: 'relative inline-flex h-5 w-9 flex-shrink-0 border-2 border-transparent disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none',
active: 'bg-primary-500 dark:bg-primary-400', rounded: 'rounded-full',
ring: 'focus-visible:ring-2 focus-visible:ring-{color}-500 dark:focus-visible:ring-{color}-400 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900',
active: 'bg-{color}-500 dark:bg-{color}-400',
inactive: 'bg-gray-200 dark:bg-gray-700', inactive: 'bg-gray-200 dark:bg-gray-700',
container: { container: {
base: 'pointer-events-none relative inline-block h-4 w-4 rounded-full bg-white dark:bg-gray-900 shadow transform ring-0 transition ease-in-out duration-200', base: 'pointer-events-none relative inline-block h-4 w-4 rounded-full bg-white dark:bg-gray-900 shadow transform ring-0 transition ease-in-out duration-200',
@@ -501,12 +517,13 @@ const toggle = {
base: 'absolute inset-0 h-full w-full flex items-center justify-center transition-opacity', base: 'absolute inset-0 h-full w-full flex items-center justify-center transition-opacity',
active: 'opacity-100 ease-in duration-200', active: 'opacity-100 ease-in duration-200',
inactive: 'opacity-0 ease-out duration-100', inactive: 'opacity-0 ease-out duration-100',
on: 'h-3 w-3 text-primary-500 dark:text-primary-400', on: 'h-3 w-3 text-{color}-500 dark:text-{color}-400',
off: 'h-3 w-3 text-gray-400 dark:text-gray-500' off: 'h-3 w-3 text-gray-400 dark:text-gray-500'
}, },
default: { default: {
onIcon: null, onIcon: null,
offIcon: null offIcon: null,
color: 'primary'
} }
} }

View File

@@ -3,7 +3,7 @@
<div class="flex items-center h-5"> <div class="flex items-center h-5">
<input <input
:id="name" :id="name"
v-model="isChecked" v-model="toggle"
:name="name" :name="name"
:required="required" :required="required"
:value="value" :value="value"
@@ -12,10 +12,8 @@
:indeterminate="indeterminate" :indeterminate="indeterminate"
type="checkbox" type="checkbox"
class="form-checkbox" class="form-checkbox"
:class="[ui.base, ui.rounded]" :class="inputClass"
v-bind="$attrs" v-bind="$attrs"
@focus="$emit('focus', $event)"
@blur="$emit('blur', $event)"
> >
</div> </div>
<div v-if="label || $slots.label" class="ml-3 text-sm"> <div v-if="label || $slots.label" class="ml-3 text-sm">
@@ -34,6 +32,7 @@
import { computed, defineComponent } from 'vue' import { computed, defineComponent } from 'vue'
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { defu } from 'defu' import { defu } from 'defu'
import { classNames } from '../../utils'
import { useAppConfig } from '#imports' import { useAppConfig } from '#imports'
// TODO: Remove // TODO: Remove
// @ts-expect-error // @ts-expect-error
@@ -80,19 +79,26 @@ export default defineComponent({
type: Boolean, type: Boolean,
default: false default: false
}, },
color: {
type: String,
default: () => appConfig.ui.checkbox.default.color,
validator (value: string) {
return appConfig.ui.colors.includes(value)
}
},
ui: { ui: {
type: Object as PropType<Partial<typeof appConfig.ui.checkbox>>, type: Object as PropType<Partial<typeof appConfig.ui.checkbox>>,
default: () => appConfig.ui.checkbox default: () => appConfig.ui.checkbox
} }
}, },
emits: ['update:modelValue', 'focus', 'blur'], emits: ['update:modelValue'],
setup (props, { emit }) { setup (props, { emit }) {
// TODO: Remove // TODO: Remove
const appConfig = useAppConfig() const appConfig = useAppConfig()
const ui = computed<Partial<typeof appConfig.ui.checkbox>>(() => defu({}, props.ui, appConfig.ui.checkbox)) const ui = computed<Partial<typeof appConfig.ui.checkbox>>(() => defu({}, props.ui, appConfig.ui.checkbox))
const isChecked = computed({ const toggle = computed({
get () { get () {
return props.modelValue return props.modelValue
}, },
@@ -101,10 +107,22 @@ export default defineComponent({
} }
}) })
const inputClass = computed(() => {
return classNames(
ui.value.base,
ui.value.rounded,
ui.value.background,
ui.value.border,
ui.value.ring.replaceAll('{color}', props.color),
ui.value.color.replaceAll('{color}', props.color)
)
})
return { return {
// eslint-disable-next-line vue/no-dupe-keys // eslint-disable-next-line vue/no-dupe-keys
ui, ui,
isChecked toggle,
inputClass
} }
} }
}) })

View File

@@ -13,8 +13,6 @@
:class="inputClass" :class="inputClass"
v-bind="$attrs" v-bind="$attrs"
@input="onInput" @input="onInput"
@focus="$emit('focus', $event)"
@blur="$emit('blur', $event)"
> >
<slot /> <slot />
@@ -140,7 +138,7 @@ export default defineComponent({
default: () => appConfig.ui.input default: () => appConfig.ui.input
} }
}, },
emits: ['update:modelValue', 'focus', 'blur'], emits: ['update:modelValue'],
setup (props, { emit, slots }) { setup (props, { emit, slots }) {
// TODO: Remove // TODO: Remove
const appConfig = useAppConfig() const appConfig = useAppConfig()

View File

@@ -3,17 +3,15 @@
<div class="flex items-center h-5"> <div class="flex items-center h-5">
<input <input
:id="`${name}-${value}`" :id="`${name}-${value}`"
v-model="isChecked" v-model="pick"
:name="name" :name="name"
:required="required" :required="required"
:value="value" :value="value"
:disabled="disabled" :disabled="disabled"
type="radio" type="radio"
class="form-radio" class="form-radio"
:class="[ui.base]" :class="inputClass"
v-bind="$attrs" v-bind="$attrs"
@focus="$emit('focus', $event)"
@blur="$emit('blur', $event)"
> >
</div> </div>
<div v-if="label || $slots.label" class="ml-3 text-sm"> <div v-if="label || $slots.label" class="ml-3 text-sm">
@@ -32,6 +30,7 @@
import { computed, defineComponent } from 'vue' import { computed, defineComponent } from 'vue'
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { defu } from 'defu' import { defu } from 'defu'
import { classNames } from '../../utils'
import { useAppConfig } from '#imports' import { useAppConfig } from '#imports'
// TODO: Remove // TODO: Remove
// @ts-expect-error // @ts-expect-error
@@ -70,19 +69,26 @@ export default defineComponent({
type: Boolean, type: Boolean,
default: false default: false
}, },
color: {
type: String,
default: () => appConfig.ui.radio.default.color,
validator (value: string) {
return appConfig.ui.colors.includes(value)
}
},
ui: { ui: {
type: Object as PropType<Partial<typeof appConfig.ui.radio>>, type: Object as PropType<Partial<typeof appConfig.ui.radio>>,
default: () => appConfig.ui.radio default: () => appConfig.ui.radio
} }
}, },
emits: ['update:modelValue', 'focus', 'blur'], emits: ['update:modelValue'],
setup (props, { emit }) { setup (props, { emit }) {
// TODO: Remove // TODO: Remove
const appConfig = useAppConfig() const appConfig = useAppConfig()
const ui = computed<Partial<typeof appConfig.ui.radio>>(() => defu({}, props.ui, appConfig.ui.radio)) const ui = computed<Partial<typeof appConfig.ui.radio>>(() => defu({}, props.ui, appConfig.ui.radio))
const isChecked = computed({ const pick = computed({
get () { get () {
return props.modelValue return props.modelValue
}, },
@@ -91,10 +97,21 @@ export default defineComponent({
} }
}) })
const inputClass = computed(() => {
return classNames(
ui.value.base,
ui.value.background,
ui.value.border,
ui.value.ring.replaceAll('{color}', props.color),
ui.value.color.replaceAll('{color}', props.color)
)
})
return { return {
// eslint-disable-next-line vue/no-dupe-keys // eslint-disable-next-line vue/no-dupe-keys
ui, ui,
isChecked pick,
inputClass
} }
} }
}) })

View File

@@ -165,7 +165,7 @@ export default defineComponent({
default: () => appConfig.ui.select default: () => appConfig.ui.select
} }
}, },
emits: ['update:modelValue', 'focus', 'blur'], emits: ['update:modelValue'],
setup (props, { emit, slots }) { setup (props, { emit, slots }) {
// TODO: Remove // TODO: Remove
const appConfig = useAppConfig() const appConfig = useAppConfig()

View File

@@ -13,8 +13,6 @@
:class="textareaClass" :class="textareaClass"
v-bind="$attrs" v-bind="$attrs"
@input="onInput" @input="onInput"
@focus="$emit('focus', $event)"
@blur="$emit('blur', $event)"
/> />
</div> </div>
</template> </template>
@@ -103,7 +101,7 @@ export default defineComponent({
default: () => appConfig.ui.textarea default: () => appConfig.ui.textarea
} }
}, },
emits: ['update:modelValue', 'focus', 'blur'], emits: ['update:modelValue'],
setup (props, { emit }) { setup (props, { emit }) {
const textarea = ref<HTMLTextAreaElement | null>(null) const textarea = ref<HTMLTextAreaElement | null>(null)

View File

@@ -3,14 +3,14 @@
v-model="active" v-model="active"
:name="name" :name="name"
:disabled="disabled" :disabled="disabled"
:class="[active ? ui.active : ui.inactive, ui.base]" :class="switchClass"
> >
<span :class="[active ? ui.container.active : ui.container.inactive, ui.container.base]"> <span :class="[active ? ui.container.active : ui.container.inactive, ui.container.base]">
<span v-if="onIcon" :class="[active ? ui.icon.active : ui.icon.inactive, ui.icon.base]" aria-hidden="true"> <span v-if="onIcon" :class="[active ? ui.icon.active : ui.icon.inactive, ui.icon.base]" aria-hidden="true">
<UIcon :name="onIcon" :class="ui.icon.on" /> <UIcon :name="onIcon" :class="onIconClass" />
</span> </span>
<span v-if="offIcon" :class="[active ? ui.icon.inactive : ui.icon.active, ui.icon.base]" aria-hidden="true"> <span v-if="offIcon" :class="[active ? ui.icon.inactive : ui.icon.active, ui.icon.base]" aria-hidden="true">
<UIcon :name="offIcon" :class="ui.icon.off" /> <UIcon :name="offIcon" :class="offIconClass" />
</span> </span>
</span> </span>
</Switch> </Switch>
@@ -22,6 +22,7 @@ import type { PropType } from 'vue'
import { defu } from 'defu' import { defu } from 'defu'
import { Switch } from '@headlessui/vue' import { Switch } from '@headlessui/vue'
import UIcon from '../elements/Icon.vue' import UIcon from '../elements/Icon.vue'
import { classNames } from '../../utils'
import { useAppConfig } from '#imports' import { useAppConfig } from '#imports'
// TODO: Remove // TODO: Remove
// @ts-expect-error // @ts-expect-error
@@ -56,6 +57,13 @@ export default defineComponent({
type: String, type: String,
default: () => appConfig.ui.toggle.default.offIcon default: () => appConfig.ui.toggle.default.offIcon
}, },
color: {
type: String,
default: () => appConfig.ui.toggle.default.color,
validator (value: string) {
return appConfig.ui.colors.includes(value)
}
},
ui: { ui: {
type: Object as PropType<Partial<typeof appConfig.ui.toggle>>, type: Object as PropType<Partial<typeof appConfig.ui.toggle>>,
default: () => appConfig.ui.toggle default: () => appConfig.ui.toggle
@@ -77,10 +85,34 @@ export default defineComponent({
} }
}) })
const switchClass = computed(()=>{
return classNames(
ui.value.base,
ui.value.rounded,
ui.value.ring.replaceAll('{color}', props.color),
(active.value ? ui.value.active : ui.value.inactive).replaceAll('{color}', props.color)
)
})
const onIconClass = computed(()=>{
return classNames(
ui.value.icon.on.replaceAll('{color}', props.color)
)
})
const offIconClass = computed(()=>{
return classNames(
ui.value.icon.off.replaceAll('{color}', props.color)
)
})
return { return {
// eslint-disable-next-line vue/no-dupe-keys // eslint-disable-next-line vue/no-dupe-keys
ui, ui,
active active,
switchClass,
onIconClass,
offIconClass
} }
} }
}) })

View File

@@ -78,8 +78,8 @@ import type { Group, Command } from '../../types/command-palette'
import UIcon from '../elements/Icon.vue' import UIcon from '../elements/Icon.vue'
import UButton from '../elements/Button.vue' import UButton from '../elements/Button.vue'
import type { Button } from '../../types/button' import type { Button } from '../../types/button'
import { classNames } from '../../utils'
import CommandPaletteGroup from './CommandPaletteGroup.vue' import CommandPaletteGroup from './CommandPaletteGroup.vue'
import { classNames } from '../../utils'
import { useAppConfig } from '#imports' import { useAppConfig } from '#imports'
// TODO: Remove // TODO: Remove
// @ts-expect-error // @ts-expect-error