Skip to content

Commit

Permalink
Merge branch 'master' into feat/popover-modality
Browse files Browse the repository at this point in the history
  • Loading branch information
atomiks authored Feb 26, 2025
2 parents b1b5c83 + afc66ab commit afaef2c
Show file tree
Hide file tree
Showing 51 changed files with 758 additions and 141 deletions.
6 changes: 5 additions & 1 deletion docs/reference/generated/alert-dialog-close.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
"description": "Allows you to replace the component’s HTML element\nwith a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
}
},
"dataAttributes": {},
"dataAttributes": {
"data-disabled": {
"description": "Present when the button is disabled."
}
},
"cssVariables": {}
}
3 changes: 3 additions & 0 deletions docs/reference/generated/alert-dialog-trigger.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
"dataAttributes": {
"data-popup-open": {
"description": "Present when the corresponding dialog is open."
},
"data-disabled": {
"description": "Present when the trigger is disabled."
}
},
"cssVariables": {}
Expand Down
6 changes: 5 additions & 1 deletion docs/reference/generated/dialog-close.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
"description": "Allows you to replace the component’s HTML element\nwith a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
}
},
"dataAttributes": {},
"dataAttributes": {
"data-disabled": {
"description": "Present when the button is disabled."
}
},
"cssVariables": {}
}
3 changes: 3 additions & 0 deletions docs/reference/generated/dialog-trigger.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
"dataAttributes": {
"data-popup-open": {
"description": "Present when the corresponding dialog is open."
},
"data-disabled": {
"description": "Present when the trigger is disabled."
}
},
"cssVariables": {}
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/accordion/header/AccordionHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const AccordionHeader = React.forwardRef(function AccordionHeader(
return renderElement();
});

export namespace AccordionHeader {
namespace AccordionHeader {
export interface Props extends BaseUIComponentProps<'h3', AccordionItem.State> {}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/accordion/item/AccordionItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ const AccordionItem = React.forwardRef(function AccordionItem(

export type AccordionItemValue = any | null;

export namespace AccordionItem {
namespace AccordionItem {
export interface State extends AccordionRoot.State {
index: number;
open: boolean;
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/accordion/panel/AccordionPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const AccordionPanel = React.forwardRef(function AccordionPanel(
return renderElement();
});

export namespace AccordionPanel {
namespace AccordionPanel {
export interface Props
extends BaseUIComponentProps<'div', AccordionItem.State>,
Pick<AccordionRoot.Props, 'hiddenUntilFound' | 'keepMounted'> {}
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/accordion/root/AccordionRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ const AccordionRoot = React.forwardRef(function AccordionRoot(
);
});

export namespace AccordionRoot {
namespace AccordionRoot {
export interface State {
value: AccordionValue;
/**
Expand Down
68 changes: 68 additions & 0 deletions packages/react/src/alert-dialog/close/AlertDialogClose.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import * as React from 'react';
import { expect } from 'chai';
import { spy } from 'sinon';
import { AlertDialog } from '@base-ui-components/react/alert-dialog';
import { screen } from '@mui/internal-test-utils';
import { createRenderer, describeConformance } from '#test-utils';

describe('<AlertDialog.Close />', () => {
Expand All @@ -18,4 +21,69 @@ describe('<AlertDialog.Close />', () => {
);
},
}));

describe('prop: disabled', () => {
it('disables the button', async () => {
const handleOpenChange = spy();

const { user } = await render(
<AlertDialog.Root onOpenChange={handleOpenChange}>
<AlertDialog.Trigger>Open</AlertDialog.Trigger>
<AlertDialog.Portal>
<AlertDialog.Popup>
<AlertDialog.Close disabled>Close</AlertDialog.Close>
</AlertDialog.Popup>
</AlertDialog.Portal>
</AlertDialog.Root>,
);

expect(handleOpenChange.callCount).to.equal(0);

const openButton = screen.getByText('Open');
await user.click(openButton);

expect(handleOpenChange.callCount).to.equal(1);
expect(handleOpenChange.firstCall.args[0]).to.equal(true);

const closeButton = screen.getByText('Close');
expect(closeButton).to.have.attribute('disabled');
expect(closeButton).to.have.attribute('data-disabled');
await user.click(closeButton);

expect(handleOpenChange.callCount).to.equal(1);
});

it('custom element', async () => {
const handleOpenChange = spy();

const { user } = await render(
<AlertDialog.Root onOpenChange={handleOpenChange}>
<AlertDialog.Trigger>Open</AlertDialog.Trigger>
<AlertDialog.Portal>
<AlertDialog.Popup>
<AlertDialog.Close disabled render={<span />}>
Close
</AlertDialog.Close>
</AlertDialog.Popup>
</AlertDialog.Portal>
</AlertDialog.Root>,
);

expect(handleOpenChange.callCount).to.equal(0);

const openButton = screen.getByText('Open');
await user.click(openButton);

expect(handleOpenChange.callCount).to.equal(1);
expect(handleOpenChange.firstCall.args[0]).to.equal(true);

const closeButton = screen.getByText('Close');
expect(closeButton).to.not.have.attribute('disabled');
expect(closeButton).to.have.attribute('data-disabled');
expect(closeButton).to.have.attribute('aria-disabled', 'true');
await user.click(closeButton);

expect(handleOpenChange.callCount).to.equal(1);
});
});
});
20 changes: 14 additions & 6 deletions packages/react/src/alert-dialog/close/AlertDialogClose.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import { useDialogClose } from '../../dialog/close/useDialogClose';
import { useComponentRenderer } from '../../utils/useComponentRenderer';
import type { BaseUIComponentProps } from '../../utils/types';

const state = {};

/**
* A button that closes the alert dialog.
* Renders a `<button>` element.
Expand All @@ -18,16 +16,17 @@ const AlertDialogClose = React.forwardRef(function AlertDialogClose(
props: AlertDialogClose.Props,
forwardedRef: React.ForwardedRef<HTMLButtonElement>,
) {
const { render, className, ...other } = props;
const { render, className, disabled = false, ...other } = props;
const { open, setOpen } = useAlertDialogRootContext();
const { getRootProps } = useDialogClose({ open, setOpen });
const { getRootProps } = useDialogClose({ disabled, open, setOpen, rootRef: forwardedRef });

const state: AlertDialogClose.State = React.useMemo(() => ({ disabled }), [disabled]);

const { renderElement } = useComponentRenderer({
render: render ?? 'button',
className,
state,
propGetter: getRootProps,
ref: forwardedRef,
extraProps: other,
});

Expand All @@ -37,7 +36,12 @@ const AlertDialogClose = React.forwardRef(function AlertDialogClose(
namespace AlertDialogClose {
export interface Props extends BaseUIComponentProps<'button', State> {}

export interface State {}
export interface State {
/**
* Whether the button is currently disabled.
*/
disabled: boolean;
}
}

AlertDialogClose.propTypes /* remove-proptypes */ = {
Expand All @@ -54,6 +58,10 @@ AlertDialogClose.propTypes /* remove-proptypes */ = {
* returns a class based on the component’s state.
*/
className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
/**
* @ignore
*/
disabled: PropTypes.bool,
/**
* Allows you to replace the component’s HTML element
* with a different tag, or compose it with another component.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export enum AlertDialogCloseDataAttributes {
/**
* Present when the button is disabled.
*/
disabled = 'data-disabled',
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as React from 'react';
import { expect } from 'chai';
import { AlertDialog } from '@base-ui-components/react/alert-dialog';
import { screen } from '@mui/internal-test-utils';
import { createRenderer, describeConformance } from '#test-utils';

describe('<AlertDialog.Trigger />', () => {
Expand All @@ -16,4 +18,55 @@ describe('<AlertDialog.Trigger />', () => {
);
},
}));

describe('prop: disabled', () => {
it('disables the dialog', async () => {
const { user } = await render(
<AlertDialog.Root>
<AlertDialog.Trigger disabled />
<AlertDialog.Portal>
<AlertDialog.Backdrop />
<AlertDialog.Popup>
<AlertDialog.Title>title text</AlertDialog.Title>
</AlertDialog.Popup>
</AlertDialog.Portal>
</AlertDialog.Root>,
);

const trigger = screen.getByRole('button');
expect(trigger).to.have.attribute('disabled');
expect(trigger).to.have.attribute('data-disabled');

await user.click(trigger);
expect(screen.queryByText('title text')).to.equal(null);

await user.keyboard('[Tab]');
expect(document.activeElement).to.not.equal(trigger);
});

it('custom element', async () => {
const { user } = await render(
<AlertDialog.Root>
<AlertDialog.Trigger disabled render={<span />} />
<AlertDialog.Portal>
<AlertDialog.Backdrop />
<AlertDialog.Popup>
<AlertDialog.Title>title text</AlertDialog.Title>
</AlertDialog.Popup>
</AlertDialog.Portal>
</AlertDialog.Root>,
);

const trigger = screen.getByRole('button');
expect(trigger).to.not.have.attribute('disabled');
expect(trigger).to.have.attribute('data-disabled');
expect(trigger).to.have.attribute('aria-disabled', 'true');

await user.click(trigger);
expect(screen.queryByText('title text')).to.equal(null);

await user.keyboard('[Tab]');
expect(document.activeElement).to.not.equal(trigger);
});
});
});
23 changes: 20 additions & 3 deletions packages/react/src/alert-dialog/trigger/AlertDialogTrigger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { useAlertDialogRootContext } from '../root/AlertDialogRootContext';
import { useButton } from '../../use-button/useButton';
import { useComponentRenderer } from '../../utils/useComponentRenderer';
import { useForkRef } from '../../utils/useForkRef';
import type { BaseUIComponentProps } from '../../utils/types';
Expand All @@ -17,18 +18,26 @@ const AlertDialogTrigger = React.forwardRef(function AlertDialogTrigger(
props: AlertDialogTrigger.Props,
forwardedRef: React.ForwardedRef<HTMLButtonElement>,
) {
const { render, className, ...other } = props;
const { render, className, disabled = false, ...other } = props;
const { open, setTriggerElement, getTriggerProps } = useAlertDialogRootContext();

const state: AlertDialogTrigger.State = React.useMemo(() => ({ open }), [open]);
const state: AlertDialogTrigger.State = React.useMemo(
() => ({ disabled, open }),
[disabled, open],
);

const mergedRef = useForkRef(forwardedRef, setTriggerElement);

const { getButtonProps } = useButton({
disabled,
buttonRef: mergedRef,
});

const { renderElement } = useComponentRenderer({
render: render ?? 'button',
className,
state,
propGetter: getTriggerProps,
propGetter: (externalProps) => getButtonProps(getTriggerProps(externalProps)),
extraProps: other,
customStyleHookMapping: triggerOpenStateMapping,
ref: mergedRef,
Expand All @@ -41,6 +50,10 @@ namespace AlertDialogTrigger {
export interface Props extends BaseUIComponentProps<'button', State> {}

export interface State {
/**
* Whether the dialog is currently disabled.
*/
disabled: boolean;
/**
* Whether the dialog is currently open.
*/
Expand All @@ -62,6 +75,10 @@ AlertDialogTrigger.propTypes /* remove-proptypes */ = {
* returns a class based on the component’s state.
*/
className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
/**
* @ignore
*/
disabled: PropTypes.bool,
/**
* Allows you to replace the component’s HTML element
* with a different tag, or compose it with another component.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
export enum AlertDialogTriggerDataAttributes {
/**
* Present when the trigger is disabled.
*/
disabled = 'data-disabled',
/**
* Present when the corresponding dialog is open.
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/avatar/fallback/AvatarFallback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const AvatarFallback = React.forwardRef<HTMLSpanElement, AvatarFallback.Props>(
},
);

export namespace AvatarFallback {
namespace AvatarFallback {
export interface Props extends BaseUIComponentProps<'span', AvatarRoot.State> {
/**
* How long to wait before showing the fallback. Specified in milliseconds.
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/avatar/image/AvatarImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const AvatarImage = React.forwardRef<HTMLImageElement, AvatarImage.Props>(functi
return imageLoadingStatus === 'loaded' ? renderElement() : null;
});

export namespace AvatarImage {
namespace AvatarImage {
export interface Props extends BaseUIComponentProps<'img', AvatarRoot.State> {
/**
* Callback fired when the loading status changes.
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/avatar/root/AvatarRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const AvatarRoot = React.forwardRef<HTMLSpanElement, AvatarRoot.Props>(function

export type ImageLoadingStatus = 'idle' | 'loading' | 'loaded' | 'error';

export namespace AvatarRoot {
namespace AvatarRoot {
export interface Props extends BaseUIComponentProps<'span', State> {}

export interface State {
Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/checkbox-group/CheckboxGroupContext.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
'use client';
import * as React from 'react';
import { UseCheckboxGroupParent } from './useCheckboxGroupParent';
import { useCheckboxGroupParent } from './useCheckboxGroupParent';

export interface CheckboxGroupContext {
value: string[] | undefined;
defaultValue: string[] | undefined;
setValue: (value: string[], event: Event) => void;
allValues: string[] | undefined;
parent: UseCheckboxGroupParent.ReturnValue;
parent: useCheckboxGroupParent.ReturnValue;
disabled: boolean;
}

Expand Down
Loading

0 comments on commit afaef2c

Please sign in to comment.