8000 feat(Radio/Checkbox/Toggle)!: handle `color` prop for form elements by Haythamasalama · Pull Request #323 · nuxt/ui · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
4 changes: 3 additions & 1 deletion docs/components/content/ComponentCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
v-if="prop.type === 'boolean'"
v-model="componentProps[prop.name]"
:name="`prop-${prop.name}`"
variant="none"
tabindex="-1"
:ui="{ wrapper: 'relative flex items-start justify-center' }"
/>
<USelectMenu
Expand All @@ -18,6 +18,7 @@
variant="none"
:ui="{ width: 'w-32 !-mt-px', rounded: 'rounded-b-md', wrapper: 'relative inline-flex' }"
class="!py-0"
tabindex="-1"
:popper="{ strategy: 'fixed', placement: 'bottom-start' }"
/>
<UInput
Expand All @@ -28,6 +29,7 @@
variant="none"
autocomplete="off"
class="!py-0"
tabindex="-1"
@update:model-value="val => componentProps[prop.name] = prop.type === 'number' ? Number(val) : val"
/>
</div>
Expand Down
2 changes: 1 addition & 1 deletion docs/components/content/examples/CheckboxExample.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup>
const selected = ref(false)
const selected = ref(true)
</script>

<template>
Expand Down
2 changes: 1 addition & 1 deletion docs/content/1.getting-started/3.theming.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
::

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.

Expand Down
20 changes: 17 additions & 3 deletions docs/content/3.forms/5.checkbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Use a `v-model` to make the Checkbox reactive.
#code
```vue
<script setup>
const selected = ref(false)
const selected = ref(true)
</script>

<template>
Expand All @@ -36,14 +36,28 @@ props:
---
::

### Style

Use the `color` prop to change the style of the Checkbox.

::component-card
---
baseProps:
name: 'checkbox2'
label: 'Label'
props:
color: 'primary'
---
::

### Required

Use the `required` prop to display a red star next to the label.

::component-card
---
baseProps:
name: 'checkbox2'
name: 'checkbox3'
props:
label: 'Label'
required: true
Expand All @@ -57,7 +71,7 @@ Use the `help` prop to display some text under the Checkbox.
::component-card
---
baseProps:
name: 'checkbox3'
name: 'checkbox4'
props:
label: 'Label'
help: 'Please check this box'
Expand Down
20 changes: 17 additions & 3 deletions docs/content/3.forms/6.radio.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,28 @@ props:
---
::

### Style

Use the `color` prop to change the style of the Radio.

::component-card
---
baseProps:
name: 'radio2'
label: 'Label'
props:
color: 'primary'
---
::

### Required

Use the `required` prop to display a red star next to the label.

::component-card
---
baseProps:
name: 'radio2'
name: 'radio3'
props:
label: 'Label'
required: true
Expand All @@ -71,7 +85,7 @@ Use the `help` prop to display some text under the Radio.
::component-card
---
baseProps:
name: 'radio3'
name: 'radio4'
props:
label: 'Label'
help: 'Please choose one'
Expand All @@ -85,7 +99,7 @@ Use the `disabled` prop to disable the Radio.
::component-card
---
baseProps:
name: 'radio4'
name: 'radio5'
value: true
props:
disabled: true
Expand Down
11 changes: 11 additions & 0 deletions docs/content/3.forms/7.toggle.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

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`.
Expand Down
2 changes: 1 addition & 1 deletion docs/content/6.overlays/6.notification.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ props:
---
::

### Color
### Style

Use the `color` prop to change the progress and icon color of the Notification.

Expand Down
43 changes: 42 additions & 1 deletion src/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,47 @@ const safelistByComponent = {
pattern: new RegExp(`ring-(${colorsAsRegex})-500`),
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) => [{
pattern: new RegExp(`bg-(${colorsAsRegex})-400`),
variants: ['dark']
Expand Down Expand Up @@ -133,7 +174,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 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 [
...safelist,
Expand Down
33 changes: 25 additions & 8 deletions src/runtime/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,24 +473,40 @@ const selectMenu = {

const radio = {
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',
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 = {
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',
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',
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 = {
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',
active: 'bg-primary-500 dark:bg-primary-400',
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',
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',
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',
Expand All @@ -501,12 +517,13 @@ const toggle = {
base: 'absolute inset-0 h-full w-full flex items-center justify-center transition-opacity',
active: 'opacity-100 ease-in duration-200',
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'
},
default: {
onIcon: null,
offIcon: null
offIcon: null,
color: 'primary'
}
}

Expand Down
32 changes: 25 additions & 7 deletions src/runtime/components/forms/Checkbox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<div class="flex items-center h-5">
<input
:id="name"
v-model="isChecked"
v-model="toggle"
:name="name"
:required="required"
:value="value"
Expand All @@ -12,10 +12,8 @@
:indeterminate="indeterminate"
type="checkbox"
class="form-checkbox"
:class="[ui.base, ui.rounded]"
:class="inputClass"
v-bind="$attrs"
@focus="$emit('focus', $event)"
@blur="$emit('blur', $event)"
>
</div>
<div v-if="label || $slots.label" class="ml-3 text-sm">
Expand All @@ -34,6 +32,7 @@
import { computed, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { defu } from 'defu'
import { classNames } from '../../utils'
import { useAppConfig } from '#imports'
// TODO: Remove
// @ts-expect-error
Expand Down Expand Up @@ -80,19 +79,26 @@ export default defineComponent({
type: Boolean,
default: false
},
color: {
type: String,
default: () => appConfig.ui.checkbox.default.color,
validator (value: string) {
return appConfig.ui.colors.includes(value)
}
},
ui: {
type: Object as PropType<Partial<typeof appConfig.ui.checkbox>>,
default: () => appConfig.ui.checkbox
}
},
emits: ['update:modelValue', 'focus', 'blur'],
emits: ['update:modelValue'],
setup (props, { emit }) {
// TODO: Remove
const appConfig = useAppConfig()

const ui = computed<Partial<typeof appConfig.ui.checkbox>>(() => defu({}, props.ui, appConfig.ui.checkbox))

const isChecked = computed({
const toggle = computed({
get () {
return props.modelValue
},
Expand All @@ -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 {
// eslint-disable-next-line vue/no-dupe-keys
ui,
isChecked
toggle,
inputClass
}
}
})
Expand Down
4 changes: 1 addition & 3 deletions src/runtime/components/forms/Input.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
:class="inputClass"
v-bind="$attrs"
@input="onInput"
@focus="$emit('focus', $event)"
@blur="$emit('blur', $event)"
>
<slot />

Expand Down Expand Up @@ -140,7 +138,7 @@ export default defineComponent({
default: () => appConfig.ui.input
}
},
emits: ['update:modelValue', 'focus', 'blur'],
emits: ['update:modelValue'],
setup (props, { emit, slots }) {
// TODO: Remove
const appConfig = useAppConfig()
Expand Down
Loading
0