Skip to content
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

fix(button): disabled priority #3133

Merged
merged 14 commits into from
Jul 25, 2023
1 change: 0 additions & 1 deletion src/button/__tests__/__snapshots__/index.test.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ exports[`Button > :props > :icon function 1`] = `
exports[`Button > :props > :loading 1`] = `
<button
class="t-button t-button--variant-base t-button--theme-primary t-is-loading"
disabled=""
href=""
type="button"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,6 @@ exports[`Button Component > props.href works fine 1`] = `
exports[`Button Component > props.loading works fine 1`] = `
<button
class="t-button t-button--variant-base t-button--theme-primary t-is-loading"
disabled=""
href=""
type="button"
>
Expand Down Expand Up @@ -213,7 +212,6 @@ exports[`Button Component > props.loading works fine 2`] = `
exports[`Button Component > props.loading: Button contains element \`.t-loading\` 1`] = `
<button
class="t-button t-button--variant-base t-button--theme-primary t-is-loading"
disabled=""
href=""
type="button"
>
Expand Down
18 changes: 9 additions & 9 deletions src/button/button.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@ name | type | default | description | required
block | Boolean | false | make button to be a block-level element | N
content | String / Slot / Function | - | button's children elements。Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
default | String / Slot / Function | - | default slot。Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
disabled | Boolean | false | disable the button, make it can not be clicked | N
disabled | Boolean | undefined | disable the button, make it can not be clicked | N
ghost | Boolean | false | make background-color to be transparent | N
href | String | - | \- | N
icon | Slot / Function | - | use it to set left icon in button。Typescript:`TNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
loading | Boolean | false | set button to be loading state | N
shape | String | rectangle | button shape。optionsrectangle/square/round/circle | N
size | String | medium | a button has three size。optionssmall/medium/large。Typescript:`SizeEnum`。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
shape | String | rectangle | button shape。options: rectangle/square/round/circle | N
size | String | medium | a button has four size。options: extra-small/small/medium/large。Typescript:`SizeEnum`。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
suffix | Slot / Function | - | Typescript:`TNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
tag | String | - | HTML Tag Element。optionsbutton/a/div | N
theme | String | - | button theme。optionsdefault/primary/danger/warning/success | N
type | String | button | type of button element in html。optionssubmit/reset/button | N
variant | String | base | variant of button。optionsbase/outline/dashed/text | N
onClick | Function | | Typescript:`(e: MouseEvent) => void`<br/> | N
tag | String | - | HTML Tag Element。options: button/a/div | N
theme | String | - | button theme。options: default/primary/danger/warning/success | N
type | String | button | type of button element in html。options: submit/reset/button | N
variant | String | base | variant of button。options: base/outline/dashed/text | N
onClick | Function | | Typescript:`(e: MouseEvent) => void`<br/>trigger on click | N

### Button Events

name | params | description
-- | -- | --
click | `(e: MouseEvent)` | \-
click | `(e: MouseEvent)` | trigger on click
4 changes: 2 additions & 2 deletions src/button/button.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
block | Boolean | false | 是否为块级元素 | N
content | String / Slot / Function | - | 按钮内容。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
default | String / Slot / Function | - | 按钮内容。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
disabled | Boolean | false | 禁用状态 | N
disabled | Boolean | undefined | 禁用状态。优先级:Button.disabled > Form.disabled | N
ghost | Boolean | false | 是否为幽灵按钮(镂空按钮) | N
href | String | - | 跳转地址。href 存在时,按钮标签默认使用 `<a>` 渲染;如果指定了 `tag` 则使用指定的标签渲染 | N
icon | Slot / Function | - | 按钮内部图标,可完全自定义。TS 类型:`TNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
loading | Boolean | false | 是否显示为加载状态 | N
shape | String | rectangle | 按钮形状,有 4 种:长方形、正方形、圆角长方形、圆形。可选项:rectangle/square/round/circle | N
size | String | medium | 组件尺寸。可选项:small/medium/large。TS 类型:`SizeEnum`。[通用类型定义](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
size | String | medium | 组件尺寸。可选项:extra-small/small/medium/large。TS 类型:`SizeEnum`。[通用类型定义](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
suffix | Slot / Function | - | 右侧内容,可用于定义右侧图标。TS 类型:`TNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N
tag | String | - | 渲染按钮的 HTML 标签,默认使用标签 `<button>` 渲染,可以自定义为 `<a>` `<div>` 等。透传全部 HTML 属性,如:`href/target/data-*` 等。⚠️ 禁用按钮 `<button disabled>`无法显示 Popup 浮层信息,可通过修改 `tag=div` 解决这个问题。可选项:button/a/div | N
theme | String | - | 组件风格,依次为默认色、品牌色、危险色、警告色、成功色。可选项:default/primary/danger/warning/success | N
Expand Down
9 changes: 5 additions & 4 deletions src/button/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { computed, defineComponent, h, ref } from 'vue';
import TLoading from '../loading';
import props from './props';
import useRipple from '../hooks/useRipple';
import { useFormDisabled } from '../form/hooks';
import { usePrefixClass, useCommonClassName } from '../hooks/useConfig';
import { useTNodeJSX, useContent } from '../hooks/tnode';
import { useDisabled } from '../hooks/useDisabled';

export default defineComponent({
name: 'TButton',
Expand All @@ -14,25 +14,26 @@ export default defineComponent({
const renderContent = useContent();
const COMPONENT_NAME = usePrefixClass('button');
const { STATUS, SIZE } = useCommonClassName();
const disabled = useFormDisabled();
const btnRef = ref<HTMLElement>();

useRipple(btnRef);

const isDisabled = computed(() => props.disabled || props.loading || disabled.value);
const isDisabled = useDisabled();

const mergeTheme = computed(() => {
const { theme, variant } = props;
if (theme) return theme;
if (variant === 'base') return 'primary';
return 'default';
});

const buttonClass = computed(() => [
`${COMPONENT_NAME.value}`,
`${COMPONENT_NAME.value}--variant-${props.variant}`,
`${COMPONENT_NAME.value}--theme-${mergeTheme.value}`,
{
[SIZE.value[props.size]]: props.size !== 'medium',
[STATUS.value.disabled]: props.disabled || disabled.value,
[STATUS.value.disabled]: isDisabled.value,
[STATUS.value.loading]: props.loading,
[`${COMPONENT_NAME.value}--shape-${props.shape}`]: props.shape !== 'rectangle',
[`${COMPONENT_NAME.value}--ghost`]: props.ghost,
Expand Down
9 changes: 6 additions & 3 deletions src/button/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ export default {
default: {
type: [String, Function] as PropType<TdButtonProps['default']>,
},
/** 禁用状态 */
disabled: Boolean,
/** 禁用状态。优先级:Button.disabled > Form.disabled */
disabled: {
type: Boolean,
default: undefined,
},
/** 是否为幽灵按钮(镂空按钮) */
ghost: Boolean,
/** 跳转地址。href 存在时,按钮标签默认使用 `<a>` 渲染;如果指定了 `tag` 则使用指定的标签渲染 */
Expand Down Expand Up @@ -48,7 +51,7 @@ export default {
default: 'medium' as TdButtonProps['size'],
validator(val: TdButtonProps['size']): boolean {
if (!val) return true;
return ['small', 'medium', 'large'].includes(val);
return ['extra-small', 'small', 'medium', 'large'].includes(val);
},
},
/** 右侧内容,可用于定义右侧图标 */
Expand Down
3 changes: 1 addition & 2 deletions src/button/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ export interface TdButtonProps {
*/
default?: string | TNode;
/**
* 禁用状态
* @default false
* 禁用状态。优先级:Button.disabled > Form.disabled
*/
disabled?: boolean;
/**
Expand Down
42 changes: 15 additions & 27 deletions src/checkbox/checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { defineComponent, ref, toRefs, inject, watch } from 'vue';
import { defineComponent, ref, toRefs, inject, watch, computed } from 'vue';
import props from './props';
import useVModel from '../hooks/useVModel';
import { useFormDisabled } from '../form/hooks';
import useRipple from '../hooks/useRipple';
import { useContent } from '../hooks/tnode';
import { useCommonClassName, usePrefixClass } from '../hooks/useConfig';
import { CheckboxGroupInjectionKey } from './constants';
import useCheckboxLazyLoad from './hooks/useCheckboxLazyLoad';
import useKeyboardEvent from './hooks/useKeyboardEvent';
import { useDisabled } from '../hooks/useDisabled';

export default defineComponent({
name: 'TCheckbox',
Expand Down Expand Up @@ -73,29 +73,17 @@ export default defineComponent({
{ immediate: true },
);

// Warn: Do not use computed to set tDisabled
// Form.disabled < CheckboxGroup.disabled < Checkbox.disabled
const tDisabled = ref<boolean>(false);
const formDisabled = useFormDisabled();
const getDisabled = () => {
const { checkAll, disabled } = props;
if (!checkAll && !tChecked.value && checkboxGroupData?.value.maxExceeded) {
// Checkbox.disabled > CheckboxGroup.disabled > Form.disabled
const beforeDisabled = computed(() => {
if (!props.checkAll && !tChecked.value && checkboxGroupData?.value.maxExceeded) {
return true;
}
if (disabled !== undefined) return disabled;
if (checkboxGroupData?.value.disabled !== undefined) {
return checkboxGroupData.value.disabled;
}
if (formDisabled.value !== undefined) return formDisabled.value;
return false;
};
watch(
() => [props.checkAll, props.disabled, tChecked.value, formDisabled.value, checkboxGroupData?.value.maxExceeded],
() => {
tDisabled.value = getDisabled();
},
{ immediate: true },
);
return null;
});
const afterDisabled = computed(() => {
return checkboxGroupData?.value.disabled;
});
const isDisabled = useDisabled({ beforeDisabled, afterDisabled });

const tIndeterminate = ref(false);
watch(
Expand All @@ -110,13 +98,13 @@ export default defineComponent({
const COMPONENT_NAME = usePrefixClass('checkbox');
const labelClasses = ref({});
watch(
[tChecked, tDisabled, tIndeterminate],
[tChecked, isDisabled, tIndeterminate],
() => {
labelClasses.value = [
`${COMPONENT_NAME.value}`,
{
[STATUS.value.checked]: tChecked.value,
[STATUS.value.disabled]: tDisabled.value,
[STATUS.value.disabled]: isDisabled.value,
[STATUS.value.indeterminate]: tIndeterminate.value,
},
];
Expand Down Expand Up @@ -148,7 +136,7 @@ export default defineComponent({
<label
ref={labelRef}
class={labelClasses.value}
tabindex={tDisabled.value ? undefined : '0'}
tabindex={isDisabled.value ? undefined : '0'}
onFocus={onCheckboxFocus}
onBlur={onCheckboxBlur}
>
Expand All @@ -159,7 +147,7 @@ export default defineComponent({
type="checkbox"
tabindex="-1"
class={`${COMPONENT_NAME.value}__former`}
disabled={tDisabled.value}
disabled={isDisabled.value}
readonly={props.readonly}
indeterminate={tIndeterminate.value}
name={tName.value}
Expand Down
11 changes: 11 additions & 0 deletions src/form/_example/disabled.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@
<t-space size="small">
<t-button theme="primary" type="submit">提交</t-button>
<t-button theme="default" variant="base" type="reset">重置</t-button>
<t-button
theme="primary"
variant="base"
:disabled="false"
@click="
() => {
formDisabled = !formDisabled;
}
"
>{{ formDisabled ? '关闭' : '开启' }}禁用表单</t-button
>
</t-space>
</t-form-item>
</t-form>
Expand Down
36 changes: 36 additions & 0 deletions src/hooks/useDisabled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Ref, inject, computed, getCurrentInstance } from 'vue';
import isBoolean from 'lodash/isBoolean';
import { TdFormProps } from '../form/type';

export interface FormDisabledProvider {
disabled: Ref<TdFormProps['disabled']>;
}

export interface DisabledContext {
beforeDisabled?: Ref<boolean>;
afterDisabled?: Ref<boolean>;
}

/**
* 用于实现组件全局禁用状态的hook
* 优先级:(beforeDisabled) > Component.disabled > ComponentGroup.disabled(afterDisabled) > Form.disabled
* @returns
*/
export function useDisabled(context?: DisabledContext) {
const currentInstance = getCurrentInstance();
const componentDisabled = computed(() => currentInstance.props.disabled as boolean);

const formDisabled = inject<FormDisabledProvider>('formDisabled', Object.create(null));

return computed(() => {
if (isBoolean(context?.beforeDisabled.value)) return context.beforeDisabled.value;
// Component
if (isBoolean(componentDisabled.value)) return componentDisabled.value;
// ComponentGroup
if (isBoolean(context?.afterDisabled.value)) return context.afterDisabled.value;
// Form
if (isBoolean(formDisabled.disabled?.value)) return formDisabled.disabled.value;

return false;
});
}
24 changes: 20 additions & 4 deletions test/unit/snap/__snapshots__/csr.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -7587,7 +7587,6 @@ exports[`csr snapshot test > csr test ./src/button/_example/loading.vue 1`] = `
>
<button
class="t-button t-button--variant-base t-button--theme-primary t-is-loading"
disabled=""
href=""
type="button"
>
Expand Down Expand Up @@ -7628,7 +7627,6 @@ exports[`csr snapshot test > csr test ./src/button/_example/loading.vue 1`] = `
</button>
<button
class="t-button t-button--variant-outline t-button--theme-default t-is-loading"
disabled=""
href=""
type="button"
>
Expand Down Expand Up @@ -8485,7 +8483,6 @@ exports[`csr snapshot test > csr test ./src/button/_example/status.vue 1`] = `
>
<button
class="t-button t-button--variant-base t-button--theme-primary t-is-loading"
disabled=""
href=""
type="button"
>
Expand Down Expand Up @@ -53720,7 +53717,6 @@ exports[`csr snapshot test > csr test ./src/dialog/_example/async.vue 1`] = `
</button>
<button
class="t-button t-button--variant-base t-button--theme-primary t-is-loading t-dialog__confirm"
disabled=""
href=""
type="button"
>
Expand Down Expand Up @@ -62880,6 +62876,26 @@ exports[`csr snapshot test > csr test ./src/form/_example/disabled.vue 1`] = `
<!---->


<div
class="t-space-item"
>
<button
class="t-button t-button--variant-base t-button--theme-primary"
href=""
type="button"
>
<span
class="t-button__text"
>

关闭禁用表单

</span>
</button>
</div>
<!---->


</div>

<!---->
Expand Down
Loading
Loading