From 5ea43ab4e465fc78c6e5036f584e40f9308b77dd Mon Sep 17 00:00:00 2001 From: Benjamin Canac Date: Wed, 31 May 2023 23:30:52 +0200 Subject: [PATCH] chore: uniformize icons in `Button` / `Input` / `Select` / `SelectMenu` Also adds `loading` to `Select` and `SelectMenu` --- docs/content/1.getting-started/3.theming.md | 1 + docs/content/3.forms/3.select.md | 23 +++++ docs/content/3.forms/4.select-menu.md | 16 ++-- src/runtime/app.config.ts | 1 + src/runtime/components/forms/Input.vue | 32 ++++--- src/runtime/components/forms/Select.vue | 97 +++++++++++++++++---- src/runtime/components/forms/SelectMenu.vue | 93 ++++++++++++++++---- 7 files changed, 206 insertions(+), 57 deletions(-) diff --git a/docs/content/1.getting-started/3.theming.md b/docs/content/1.getting-started/3.theming.md index c8eb87d8..f37fbacd 100644 --- a/docs/content/1.getting-started/3.theming.md +++ b/docs/content/1.getting-started/3.theming.md @@ -166,6 +166,7 @@ export default defineAppConfig({ }, select: { default: { + loadingIcon: 'i-octicon-sync-24', trailingIcon: 'i-octicon-chevron-down-24' } }, diff --git a/docs/content/3.forms/3.select.md b/docs/content/3.forms/3.select.md index 854f4d8d..70a230be 100644 --- a/docs/content/3.forms/3.select.md +++ b/docs/content/3.forms/3.select.md @@ -157,6 +157,29 @@ props: --- :: +### Loading + +Use the `loading` prop to show a loading icon and disable the Input. + +Use the `loading-icon` prop to set a different icon or change it globally in `ui.select.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`. + +::component-card +--- +baseProps: + name: 'select' + options: + - 'United States' + - 'Canada' + - 'Mexico' + placeholder: 'Search...' +props: + loading: true + icon: 'i-heroicons-magnifying-glass-20-solid' +excludedProps: + - icon +--- +:: + ## Props :component-props diff --git a/docs/content/3.forms/4.select-menu.md b/docs/content/3.forms/4.select-menu.md index 8cacdbd8..2775c9ff 100644 --- a/docs/content/3.forms/4.select-menu.md +++ b/docs/content/3.forms/4.select-menu.md @@ -162,10 +162,6 @@ const selected = ref(people[0]) ### Icon -Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`. - -Use the `trailing-icon` prop to set a different icon or change it globally in `ui.select.default.trailingIcon`. Defaults to `i-heroicons-chevron-down-20-solid`. - Use the `selected-icon` prop to set a different icon or change it globally in `ui.selectMenu.default.selectedIcon`. Defaults to `i-heroicons-check-20-solid`. ::component-card @@ -175,16 +171,16 @@ baseProps: placeholder: 'Select a person' options: ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer'] props: - icon: 'i-heroicons-magnifying-glass-20-solid' - color: 'white' -extraColors: - - white - - gray + selectedIcon: 'i-heroicons-hand-thumb-up-solid' excludedProps: - - icon + - selectedIcon --- :: +::alert{icon="i-heroicons-light-bulb"} +Learn how to customize icons from the [Select](/forms/select#icon) component. +:: + ### Search Use the `searchable` prop to enable search. diff --git a/src/runtime/app.config.ts b/src/runtime/app.config.ts index 190fea84..4b4ed093 100644 --- a/src/runtime/app.config.ts +++ b/src/runtime/app.config.ts @@ -399,6 +399,7 @@ const select = { size: 'sm', color: 'white', variant: 'outline', + loadingIcon: 'i-heroicons-arrow-path-20-solid', trailingIcon: 'i-heroicons-chevron-down-20-solid' } } diff --git a/src/runtime/components/forms/Input.vue b/src/runtime/components/forms/Input.vue index 8a8f25e4..bdcca4c4 100644 --- a/src/runtime/components/forms/Input.vue +++ b/src/runtime/components/forms/Input.vue @@ -18,11 +18,11 @@ @blur="$emit('blur', $event)" > -
- +
+
-
- +
+
@@ -211,7 +211,14 @@ export default defineComponent({ return props.trailingIcon || props.icon }) - const iconClass = computed(() => { + const leadingWrapperIconClass = computed(() => { + return classNames( + ui.value.icon.leading.wrapper, + ui.value.icon.leading.padding[props.size] + ) + }) + + const leadingIconClass = computed(() => { return classNames( ui.value.icon.base, appConfig.ui.colors.includes(props.color) && ui.value.icon.color.replaceAll('{color}', props.color), @@ -220,17 +227,19 @@ export default defineComponent({ ) }) - const leadingIconClass = computed(() => { + const trailingWrapperIconClass = computed(() => { return classNames( - ui.value.icon.leading.wrapper, - ui.value.icon.leading.padding[props.size] + ui.value.icon.trailing.wrapper, + ui.value.icon.trailing.padding[props.size] ) }) const trailingIconClass = computed(() => { return classNames( - ui.value.icon.trailing.wrapper, - ui.value.icon.trailing.padding[props.size] + ui.value.icon.base, + appConfig.ui.colors.includes(props.color) && ui.value.icon.color.replaceAll('{color}', props.color), + ui.value.icon.size[props.size], + props.loading && !isLeading.value && 'animate-spin' ) }) @@ -241,11 +250,12 @@ export default defineComponent({ isLeading, isTrailing, inputClass, - iconClass, leadingIconName, leadingIconClass, + leadingWrapperIconClass, trailingIconName, trailingIconClass, + trailingWrapperIconClass, onInput } } diff --git a/src/runtime/components/forms/Select.vue b/src/runtime/components/forms/Select.vue index b4eb9688..492ac4e4 100644 --- a/src/runtime/components/forms/Select.vue +++ b/src/runtime/components/forms/Select.vue @@ -5,7 +5,7 @@ :name="name" :value="modelValue" :required="required" - :disabled="disabled" + :disabled="disabled || loading" :class="selectClass" @input="onInput" > @@ -36,12 +36,12 @@ -
- +
+
- -
@@ -89,18 +89,38 @@ export default defineComponent({ type: String, default: null }, + loadingIcon: { + type: String, + default: () => appConfig.ui.input.default.loadingIcon + }, + leadingIcon: { + type: String, + default: null + }, trailingIcon: { type: String, default: () => appConfig.ui.select.default.trailingIcon }, - options: { - type: Array, - default: () => [] + trailing: { + type: Boolean, + default: false + }, + leading: { + type: Boolean, + default: false + }, + loading: { + type: Boolean, + default: false }, padded: { type: Boolean, default: true }, + options: { + type: Array, + default: () => [] + }, size: { type: String, default: () => appConfig.ui.select.default.size, @@ -210,43 +230,82 @@ export default defineComponent({ ui.value.size[props.size], props.padded && ui.value.padding[props.size], variant?.replaceAll('{color}', props.color), - !!props.icon && ui.value.leading.padding[props.size], - ui.value.trailing.padding[props.size], + isLeading.value && ui.value.leading.padding[props.size], + isTrailing.value && ui.value.trailing.padding[props.size], ui.value.custom ) }) - const iconClass = computed(() => { - return classNames( - ui.value.icon.base, - appConfig.ui.colors.includes(props.color) && ui.value.icon.color.replaceAll('{color}', props.color), - ui.value.icon.size[props.size] - ) + const isLeading = computed(() => { + return (props.icon && props.leading) || (props.icon && !props.trailing) || (props.loading && !props.trailing) || props.leadingIcon }) - const leadingIconClass = computed(() => { + const isTrailing = computed(() => { + return (props.icon && props.trailing) || (props.loading && props.trailing) || props.trailingIcon + }) + + const leadingIconName = computed(() => { + if (props.loading) { + return props.loadingIcon + } + + return props.leadingIcon || props.icon + }) + + const trailingIconName = computed(() => { + if (props.loading && !isLeading.value) { + return props.loadingIcon + } + + return props.trailingIcon || props.icon + }) + + const leadingWrapperIconClass = computed(() => { return classNames( ui.value.icon.leading.wrapper, ui.value.icon.leading.padding[props.size] ) }) - const trailingIconClass = computed(() => { + const leadingIconClass = computed(() => { + return classNames( + ui.value.icon.base, + appConfig.ui.colors.includes(props.color) && ui.value.icon.color.replaceAll('{color}', props.color), + ui.value.icon.size[props.size], + props.loading && 'animate-spin' + ) + }) + + const trailingWrapperIconClass = computed(() => { return classNames( ui.value.icon.trailing.wrapper, ui.value.icon.trailing.padding[props.size] ) }) + const trailingIconClass = computed(() => { + return classNames( + ui.value.icon.base, + appConfig.ui.colors.includes(props.color) && ui.value.icon.color.replaceAll('{color}', props.color), + ui.value.icon.size[props.size], + props.loading && !isLeading.value && 'animate-spin' + ) + }) + return { // eslint-disable-next-line vue/no-dupe-keys ui, normalizedOptionsWithPlaceholder, normalizedValue, + isLeading, + isTrailing, selectClass, - iconClass, + leadingIconName, leadingIconClass, + leadingWrapperIconClass, + trailingIconName, trailingIconClass, + trailingWrapperIconClass, onInput } } diff --git a/src/runtime/components/forms/SelectMenu.vue b/src/runtime/components/forms/SelectMenu.vue index a683801a..48d95757 100644 --- a/src/runtime/components/forms/SelectMenu.vue +++ b/src/runtime/components/forms/SelectMenu.vue @@ -27,10 +27,10 @@ role="button" class="inline-flex w-full" > - - @@ -167,10 +167,30 @@ export default defineComponent({ type: String, default: null }, + loadingIcon: { + type: String, + default: () => appConfig.ui.input.default.loadingIcon + }, + leadingIcon: { + type: String, + default: null + }, trailingIcon: { type: String, default: () => appConfig.ui.select.default.trailingIcon }, + trailing: { + type: Boolean, + default: false + }, + leading: { + type: Boolean, + default: false + }, + loading: { + type: Boolean, + default: false + }, selectedIcon: { type: String, default: () => appConfig.ui.selectMenu.default.selectedIcon @@ -274,35 +294,69 @@ export default defineComponent({ uiSelect.value.gap[props.size], props.padded && uiSelect.value.padding[props.size], variant?.replaceAll('{color}', props.color), - !!props.icon && uiSelect.value.leading.padding[props.size], - uiSelect.value.trailing.padding[props.size], + isLeading.value && uiSelect.value.leading.padding[props.size], + isTrailing.value && uiSelect.value.trailing.padding[props.size], uiSelect.value.custom, 'inline-flex items-center' ) }) - const iconClass = computed(() => { - return classNames( - uiSelect.value.icon.base, - appConfig.ui.colors.includes(props.color) && uiSelect.value.icon.color.replaceAll('{color}', props.color), - uiSelect.value.icon.size[props.size] - ) + const isLeading = computed(() => { + return (props.icon && props.leading) || (props.icon && !props.trailing) || (props.loading && !props.trailing) || props.leadingIcon }) - const leadingIconClass = computed(() => { + const isTrailing = computed(() => { + return (props.icon && props.trailing) || (props.loading && props.trailing) || props.trailingIcon + }) + + const leadingIconName = computed(() => { + if (props.loading) { + return props.loadingIcon + } + + return props.leadingIcon || props.icon + }) + + const trailingIconName = computed(() => { + if (props.loading && !isLeading.value) { + return props.loadingIcon + } + + return props.trailingIcon || props.icon + }) + + const leadingWrapperIconClass = computed(() => { return classNames( uiSelect.value.icon.leading.wrapper, uiSelect.value.icon.leading.padding[props.size] ) }) - const trailingIconClass = computed(() => { + const leadingIconClass = computed(() => { + return classNames( + uiSelect.value.icon.base, + appConfig.ui.colors.includes(props.color) && uiSelect.value.icon.color.replaceAll('{color}', props.color), + uiSelect.value.icon.size[props.size], + props.loading && 'animate-spin' + ) + }) + + const trailingWrapperIconClass = computed(() => { return classNames( uiSelect.value.icon.trailing.wrapper, uiSelect.value.icon.trailing.padding[props.size] ) }) + const trailingIconClass = computed(() => { + return classNames( + uiSelect.value.icon.base, + appConfig.ui.colors.includes(props.color) && uiSelect.value.icon.color.replaceAll('{color}', props.color), + uiSelect.value.icon.size[props.size], + props.loading && !isLeading.value && 'animate-spin' + ) + }) + const filteredOptions = computed(() => query.value === '' ? props.options @@ -339,10 +393,15 @@ export default defineComponent({ ui, trigger, container, + isLeading, + isTrailing, selectMenuClass, - iconClass, + leadingIconName, leadingIconClass, + leadingWrapperIconClass, + trailingIconName, trailingIconClass, + trailingWrapperIconClass, filteredOptions, queryOption, query,