From 121c75b27653d57f04b2577a47b7c0628dcf1e9e Mon Sep 17 00:00:00 2001 From: Yeonju Lee Date: Sat, 15 Nov 2025 10:07:45 +0900 Subject: [PATCH 1/6] feat(button): add loadingPosition prop to control loading icon placement Add a new `loadingPosition` prop to the Button component that allows developers to control where the loading icon appears when the button is in a loading state. The prop accepts three values: - 'left': displays loading icon on the left (default behavior) - 'center': displays loading icon in the center, hiding the label - 'right': displays loading icon on the right The implementation maintains full backward compatibility. Existing usage with `leading` and `trailing` props for loading buttons continues to work without any changes. When `loadingPosition` is not specified, the component automatically determines the position based on the `trailing` prop, preserving the existing behavior. Adds test cases for all three loadingPosition values to ensure proper rendering and behavior. --- .vscode/settings.json | 7 +++ .../nuxt/app/pages/components/button.vue | 5 +- src/runtime/components/Button.vue | 60 ++++++++++++++++--- test/components/Button.spec.ts | 3 + .../__snapshots__/Button.spec.ts.snap | 19 ++++++ 5 files changed, 83 insertions(+), 11 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..0fb80e115d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "i18n-ally.localesPaths": [ + "src/runtime/locale", + "src/runtime/components/locale", + "docs/app/components/content/examples/locale" + ] +} \ No newline at end of file diff --git a/playgrounds/nuxt/app/pages/components/button.vue b/playgrounds/nuxt/app/pages/components/button.vue index a8d25610eb..b733ad23c2 100644 --- a/playgrounds/nuxt/app/pages/components/button.vue +++ b/playgrounds/nuxt/app/pages/components/button.vue @@ -28,8 +28,9 @@ function onClick() { - - + + + diff --git a/src/runtime/components/Button.vue b/src/runtime/components/Button.vue index defb42f768..c800ad391e 100644 --- a/src/runtime/components/Button.vue +++ b/src/runtime/components/Button.vue @@ -30,6 +30,11 @@ export interface ButtonProps extends UseComponentIconsProps, Omit void | Promise) | Array<((event: MouseEvent) => void | Promise)> class?: any ui?: Button['slots'] @@ -83,9 +88,41 @@ const isLoading = computed(() => { return props.loading || (props.loadingAuto && (loadingAutoState.value || (formLoading?.value && props.type === 'submit'))) }) -const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons( - computed(() => ({ ...props, loading: isLoading.value })) -) +// loadingPosition이 명시되지 않았을 때는 기존 trailing prop 동작 유지 +const loadingPosition = computed(() => { + if (props.loadingPosition) { + return props.loadingPosition + } + // 기존 동작: trailing prop이 있으면 'right', 없으면 'left' + return props.trailing ? 'right' : 'left' +}) + +// loadingPosition에 따라 trailing prop 조정 +const iconProps = computed(() => { + const baseProps = { ...props, loading: isLoading.value } + + if (isLoading.value) { + if (loadingPosition.value === 'right') { + return { ...baseProps, trailing: true } + } else if (loadingPosition.value === 'left') { + return { ...baseProps, trailing: false } + } + // center일 때는 useComponentIcons에서 처리하지 않고 직접 처리 + return { ...baseProps, trailing: false } + } + + return baseProps +}) + +const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(iconProps) + +// center일 때 사용할 로딩 아이콘 +const centerLoadingIcon = computed(() => { + if (isLoading.value && loadingPosition.value === 'center') { + return props.loadingIcon || appConfig.ui.icons.loading + } + return undefined +}) const ui = computed(() => tv({ extend: tv(theme), @@ -134,18 +171,23 @@ const ui = computed(() => tv({ @click="onClickWrapper" > - - + + - - {{ label }} - + + - + diff --git a/test/components/Button.spec.ts b/test/components/Button.spec.ts index 75afdf4f23..c98877e446 100644 --- a/test/components/Button.spec.ts +++ b/test/components/Button.spec.ts @@ -32,6 +32,9 @@ describe('Button', () => { ['with loading and avatar', { props: { loading: true, avatar: { src: 'https://github.com/benjamincanac.png' } } }], ['with loading trailing', { props: { loading: true, trailing: true } }], ['with loading trailing and avatar', { props: { loading: true, trailing: true, avatar: { src: 'https://github.com/benjamincanac.png' } } }], + ['with loadingPosition left', { props: { loading: true, loadingPosition: 'left', label: 'Button' } }], + ['with loadingPosition center', { props: { loading: true, loadingPosition: 'center', label: 'Button' } }], + ['with loadingPosition right', { props: { loading: true, loadingPosition: 'right', label: 'Button' } }], ['with loadingIcon', { props: { loading: true, loadingIcon: 'i-lucide-loader' } }], ['with disabled', { props: { label: 'Button', disabled: true } }], ['with disabled and with link', { props: { label: 'Button', disabled: true, to: '/link' } }], diff --git a/test/components/__snapshots__/Button.spec.ts.snap b/test/components/__snapshots__/Button.spec.ts.snap index 29de5cb900..8ab2aeeb15 100644 --- a/test/components/__snapshots__/Button.spec.ts.snap +++ b/test/components/__snapshots__/Button.spec.ts.snap @@ -187,6 +187,25 @@ exports[`Button > renders with loadingIcon correctly 1`] = ` " `; +exports[`Button > renders with loadingPosition center correctly 1`] = ` +"" +`; + +exports[`Button > renders with loadingPosition left correctly 1`] = ` +"" +`; + +exports[`Button > renders with loadingPosition right correctly 1`] = ` +"" +`; + exports[`Button > renders with neutral variant ghost correctly 1`] = ` "" `; +exports[`Button > renders with loadingPosition center correctly 1`] = ` +"" +`; + +exports[`Button > renders with loadingPosition left correctly 1`] = ` +"" +`; + +exports[`Button > renders with loadingPosition right correctly 1`] = ` +"" +`; + exports[`Button > renders with neutral variant ghost correctly 1`] = ` "