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

Support ref and element attribute #124

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
3cf9b28
Accordion allows attributes of the detail element
8845musign Jul 25, 2024
81a51ba
Allow attributes of div in ActionHalfModal
8845musign Jul 25, 2024
9f1fee4
Allow attributes of div in ActionModal
8845musign Jul 25, 2024
4c32ac9
Standardized to ComponentPropsWithRef
8845musign Jul 25, 2024
d07e710
Standardized to ComponentPropsWithRef
8845musign Jul 25, 2024
f748334
Allow span attributes in Color
8845musign Jul 25, 2024
b30f197
Standardized to ComponentPropsWithRef
8845musign Jul 25, 2024
f9d971f
Allows fieldset attributes in CheckboxGroup
8845musign Jul 25, 2024
5384bcd
Allow attributes of p in ErrorMessage
8845musign Jul 25, 2024
878b8ea
Standardized to ComponentPropsWithRef
8845musign Jul 25, 2024
b864321
Standardized to ComponentPropsWithRef
8845musign Jul 25, 2024
45b89e0
Standardized to ComponentPropsWithRef
8845musign Jul 25, 2024
5f96b9f
Delete unnecessary type
8845musign Jul 25, 2024
d36459d
Allow refs to HelperMesssage and p attributes
8845musign Jul 25, 2024
26830ee
Allow refs in icons and allow svg attributes
8845musign Jul 25, 2024
4cf695c
Correction of errors in the text
8845musign Jul 25, 2024
3904cb8
Standardized to ComponentPropsWithRef
8845musign Jul 25, 2024
51e2f61
Allow attributes of the a element in LinkCards
8845musign Jul 25, 2024
9e289be
fix type error
8845musign Jul 25, 2024
7b5740e
Allow the attribute of the div in MessageHalfModal to receive the ref
8845musign Jul 25, 2024
4d04b74
Conditional Branch Merged
8845musign Jul 25, 2024
c5c9232
receive a ref
8845musign Jul 25, 2024
83f717c
Allows attributes of divs in MessageModal
8845musign Jul 25, 2024
b802964
Standardized to ComponentPropsWithRef
8845musign Jul 25, 2024
3c0d3c4
Disallowed className
8845musign Jul 25, 2024
b4e5a6b
Standardized to ComponentPropsWithRef
8845musign Jul 25, 2024
1ddc805
Add ref test
8845musign Aug 1, 2024
f560546
Allows attributes of fieldset in RadiGroup
8845musign Aug 1, 2024
a4bb83e
Allows attributes of select element in Select Component
8845musign Aug 1, 2024
622a2c9
Allows attributes of textarea element in Textarea Component
8845musign Aug 1, 2024
348f11f
Prohibits the specification of components to as
8845musign Aug 1, 2024
c2e168a
Add snapshot tests
8845musign Aug 2, 2024
ac33853
Release attributes and ref
8845musign Aug 2, 2024
0cb84bc
Add Snapshot Tests
8845musign Aug 2, 2024
e2d29b5
Fix Test
8845musign Aug 2, 2024
7acbf47
More detailed comment
8845musign Aug 2, 2024
a7809c4
default as value was not inferred.
8845musign Aug 2, 2024
a084acb
Add ref to the test code so that you can detect type errors
8845musign Aug 2, 2024
5d3f852
Release attributes and ref
8845musign Aug 2, 2024
baf454d
Release attributes
8845musign Aug 2, 2024
83e3608
Add snapshot tests
8845musign Aug 2, 2024
0f15a78
delete extra test
8845musign Aug 2, 2024
a84d990
Release attributes & ref
8845musign Aug 2, 2024
4455523
Enhanced explanation of as
8845musign Aug 2, 2024
5e4c927
update snapshots
8845musign Aug 2, 2024
7cdcaf5
fix markup error
8845musign Aug 8, 2024
7fc1206
update snapshot
8845musign Aug 8, 2024
92b034e
support ref
8845musign Aug 8, 2024
19b1e31
improve types
8845musign Aug 8, 2024
e6ad183
not allow className
8845musign Aug 8, 2024
d4f99dd
not allow className
8845musign Aug 8, 2024
7001b27
value isn't required
8845musign Aug 8, 2024
806623c
Allow refs and attributes
8845musign Aug 8, 2024
90754bb
Allow refs and attributes
8845musign Aug 8, 2024
919c665
delete extra comments
8845musign Aug 8, 2024
432c927
allow attributes
8845musign Aug 8, 2024
b555b91
fix lint errors
8845musign Aug 8, 2024
1460050
Added warning if an unexpected as value is specified.
8845musign Aug 8, 2024
e0c0d53
add prop comment
8845musign Aug 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/Accordion/Accordion.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Meta, { Id as IdStory } from './Accordion.stories';

const IdTest = composeStory(IdStory, Meta);

describe('Accorion', () => {
describe('Accordion', () => {
test('Add ids', async () => {
render(<IdTest />);

Expand Down
3 changes: 1 addition & 2 deletions src/components/Accordion/Accordion.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,9 @@ export const Small: Story = {
};

export const CustomDataAttribute: Story = {
render: (args) => <Accordion {...args}></Accordion>,
render: (args) => <Accordion data-test-id="some-id" {...args}></Accordion>,
args: {
...defaultArgs,
['data-test-id']: 'some-id',
},
};

Expand Down
36 changes: 18 additions & 18 deletions src/components/Accordion/Accordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import { ArrowBDownIcon } from '@ubie/ubie-icons';
import clsx from 'clsx';
import { ComponentPropsWithRef, forwardRef } from 'react';
import styles from './Accordion.module.css';
import { CustomDataAttributeProps } from '../../types/attributes';
import type { FC, ReactNode } from 'react';
import type { ReactNode } from 'react';

export type Size = 'small' | 'medium';

Expand All @@ -22,10 +22,6 @@ type Props = {
* @default medium
*/
size?: Size;
/**
* ラッパーであるdetails要素に付与するネイティブ要素の`id`属性。ページで固有のIDを指定
*/
id?: string;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The prop of the native element, which we had dared to define for the sake of adding explanation, is deleted.

/**
* 開閉をトリガーするsummary要素に付与するネイティブ要素の`id`属性。ページで固有のIDを指定
*/
Expand All @@ -34,16 +30,20 @@ type Props = {
* 初期状態で開く
*/
initialOpen?: boolean;
} & CustomDataAttributeProps;
} & Omit<ComponentPropsWithRef<'details'>, 'className'>;

export const Accordion = forwardRef<HTMLDetailsElement, Props>(
({ header, children, size = 'medium', id, buttonId, initialOpen, ...props }, ref) => {
return (
<details className={clsx(styles.container, styles[size])} id={id} {...props} open={initialOpen} ref={ref}>
<summary id={buttonId} className={styles.button}>
<span>{header}</span>
<ArrowBDownIcon aria-hidden className={styles.arrow} />
</summary>
<div className={styles.panel}>{children}</div>
</details>
);
},
);

export const Accordion: FC<Props> = ({ header, children, size = 'medium', id, buttonId, initialOpen, ...props }) => {
return (
<details className={clsx(styles.container, styles[size])} id={id} {...props} open={initialOpen}>
<summary id={buttonId} className={styles.button}>
<span>{header}</span>
<ArrowBDownIcon aria-hidden className={styles.arrow} />
</summary>
<div className={styles.panel}>{children}</div>
</details>
);
};
Accordion.displayName = 'Accordion';
17 changes: 17 additions & 0 deletions src/components/ActionHalfModal/ActionHalfModal.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { composeStory } from '@storybook/react';
import { render, screen } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';
import { createRef } from 'react';
import { ActionHalfModal } from './ActionHalfModal';
import Meta, { WithId, CustomHeader, Default } from './ActionHalfModal.stories';

const WithIdStory = composeStory(WithId, Meta);
Expand Down Expand Up @@ -51,4 +53,19 @@ describe('ActionHalfModal', () => {
expect(dialogElement).toHaveAttribute('aria-labelledby');
expect(dialogHeadingElement).toHaveAttribute('id', dialogElement.getAttribute('aria-labelledby'));
});

test('receives ref', () => {
// eslint-disable-next-line @typescript-eslint/no-empty-function
const handleClose = () => {};
const ref = createRef<HTMLDivElement>();

render(
<ActionHalfModal header="header" ref={ref} open onClose={handleClose}>
content
</ActionHalfModal>,
);

expect(ref.current).not.toBeNull();
expect(ref.current?.tagName).toBe('DIV');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export const NoCloseButton: Story = {
Open Modal
</button>
<ActionHalfModal
data-test-id="some-id"
onPrimaryAction={onClose}
primaryActionLabel="確認"
showClose={false}
Expand All @@ -136,7 +137,6 @@ export const NoCloseButton: Story = {
},
args: {
...defaultArgs,
[`data-test-id`]: 'some-id',
},
};

Expand Down
232 changes: 126 additions & 106 deletions src/components/ActionHalfModal/ActionHalfModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

import { Dialog, Transition } from '@headlessui/react';
import { clsx } from 'clsx';
import { type FC, Fragment, type ReactNode, useCallback, useRef } from 'react';
import { ComponentPropsWithRef, forwardRef, Fragment, type ReactNode, useCallback, useRef } from 'react';
import styles from './ActionHalfModal.module.css';
import { VisuallyHidden } from '../../sharedComponents/VisuallyHidden/VisuallyHidden';
import { CustomDataAttributeProps } from '../../types/attributes';
import { opacityToClassName } from '../../utils/style';
import { AllOrNone } from '../../utils/types';
import { Button } from '../Button/Button';
Expand Down Expand Up @@ -100,124 +99,145 @@ type SecondaryActionProps = {
secondaryActionLabel: string;
};

type Props = BaseProps & AllOrNone<PrimaryActionProps> & AllOrNone<SecondaryActionProps> & CustomDataAttributeProps;
type Props = BaseProps &
AllOrNone<PrimaryActionProps> &
AllOrNone<SecondaryActionProps> &
Omit<ComponentPropsWithRef<'div'>, 'className'>;

export const ActionHalfModal: FC<Props> = ({
children,
onClose,
onPrimaryAction,
onSecondaryAction,
header,
primaryActionLabel,
secondaryActionLabel,
primaryActionColor,
closeLabel = '閉じる',
overlayOpacity = 'normal',
showClose = true,
open = true,
isStatic = false,
fullscreen = false,
bodyScroll = true,
ariaLabelledby,
hero,
...props
}) => {
const opacityClassName = opacityToClassName(overlayOpacity);
export const ActionHalfModal = forwardRef<HTMLDivElement, Props>(
(
{
children,
onClose,
onPrimaryAction,
onSecondaryAction,
header,
primaryActionLabel,
secondaryActionLabel,
primaryActionColor,
closeLabel = '閉じる',
overlayOpacity = 'normal',
showClose = true,
open = true,
isStatic = false,
fullscreen = false,
bodyScroll = true,
ariaLabelledby,
hero,
...props
},
ref,
) => {
const opacityClassName = opacityToClassName(overlayOpacity);

const initialFocusRef = useRef(null);
const initialFocusRef = useRef(null);

const dialogRef = useCallback(
(node: HTMLDivElement | null) => {
if (node !== null && header == null && ariaLabelledby != null) {
node.setAttribute('aria-labelledby', ariaLabelledby);
} else if (node !== null && header == null && ariaLabelledby == null) {
node.removeAttribute('aria-labelledby');
}
},
[ariaLabelledby, header],
);
const dialogRef = useCallback(
(node: HTMLDivElement | null) => {
if (node !== null && header == null && ariaLabelledby != null) {
node.setAttribute('aria-labelledby', ariaLabelledby);
} else if (node !== null && header == null && ariaLabelledby == null) {
node.removeAttribute('aria-labelledby');
}

return (
<Transition show={open}>
<Dialog
ref={dialogRef}
static={isStatic}
onClose={onClose}
className={clsx(styles.modal, fullscreen && styles.fullscreen)}
initialFocus={initialFocusRef}
{...props}
>
<Transition.Child
as={Fragment}
enter={styles.overlayEnter}
enterFrom={styles.overlayEnterFrom}
enterTo={styles.overlayEnterTo}
leave={styles.overlayLeave}
leaveFrom={styles.overlayLeaveFrom}
leaveTo={styles.overlayLeaveTo}
>
<Dialog.Overlay className={clsx(styles.overlay, styles[opacityClassName])} />
</Transition.Child>
<Transition.Child
as={Fragment}
enter={styles.panelEnter}
enterFrom={styles.panelEnterFrom}
enterTo={styles.panelEnterTo}
leave={styles.panelLeave}
leaveFrom={styles.panelLeaveFrom}
leaveTo={styles.panelLeaveTo}
if (typeof ref === 'function') {
ref(node);
} else if (ref != null) {
ref.current = node;
}
},
[ariaLabelledby, header, ref],
);

return (
<Transition show={open}>
<Dialog
ref={dialogRef}
static={isStatic}
onClose={onClose}
className={clsx(styles.modal, fullscreen && styles.fullscreen)}
initialFocus={initialFocusRef}
{...props}
>
<div
className={clsx(styles.dialog, {
[styles.fullscreen]: fullscreen,
[styles.bodyScroll]: bodyScroll,
})}
<Transition.Child
as={Fragment}
enter={styles.overlayEnter}
enterFrom={styles.overlayEnterFrom}
enterTo={styles.overlayEnterTo}
leave={styles.overlayLeave}
leaveFrom={styles.overlayLeaveFrom}
leaveTo={styles.overlayLeaveTo}
>
<Dialog.Overlay className={clsx(styles.overlay, styles[opacityClassName])} />
</Transition.Child>
<Transition.Child
as={Fragment}
enter={styles.panelEnter}
enterFrom={styles.panelEnterFrom}
enterTo={styles.panelEnterTo}
leave={styles.panelLeave}
leaveFrom={styles.panelLeaveFrom}
leaveTo={styles.panelLeaveTo}
>
{header === undefined ? (
<VisuallyHidden as="p" tabIndex={-1} ref={initialFocusRef}>
ダイアログ
</VisuallyHidden>
) : null}
{hero !== undefined ? <div className={styles.hero}>{hero}</div> : null}
<div
className={clsx(styles.mainContent, {
[styles.headerLess]: header === undefined && hero === undefined,
className={clsx(styles.dialog, {
[styles.fullscreen]: fullscreen,
[styles.bodyScroll]: bodyScroll,
})}
>
{header !== undefined ? (
<Dialog.Title tabIndex={-1} ref={initialFocusRef} className={styles.header}>
{header}
</Dialog.Title>
{header === undefined ? (
<VisuallyHidden as="p" tabIndex={-1} ref={initialFocusRef}>
ダイアログ
</VisuallyHidden>
) : null}
{hero !== undefined ? <div className={styles.hero}>{hero}</div> : null}
<div
className={clsx(styles.body, {
className={clsx(styles.mainContent, {
[styles.headerLess]: header === undefined && hero === undefined,
[styles.fullscreen]: fullscreen,
})}
>
{children}
</div>
<div className={styles.buttonContainer}>
{onPrimaryAction && primaryActionLabel && (
<Button block onClick={onPrimaryAction} aria-label={primaryActionLabel} variant={primaryActionColor}>
{primaryActionLabel}
</Button>
)}
{onSecondaryAction && secondaryActionLabel && (
<Button block variant="secondary" onClick={onSecondaryAction} aria-label={secondaryActionLabel}>
{secondaryActionLabel}
</Button>
)}
{showClose && (
<Button variant="text" onClick={onClose} aria-label={closeLabel}>
{closeLabel}
</Button>
)}
{header !== undefined ? (
<Dialog.Title tabIndex={-1} ref={initialFocusRef} className={styles.header}>
{header}
</Dialog.Title>
) : null}
<div
className={clsx(styles.body, {
[styles.fullscreen]: fullscreen,
})}
>
{children}
</div>
<div className={styles.buttonContainer}>
{onPrimaryAction && primaryActionLabel && (
<Button
block
onClick={onPrimaryAction}
aria-label={primaryActionLabel}
variant={primaryActionColor}
>
{primaryActionLabel}
</Button>
)}
{onSecondaryAction && secondaryActionLabel && (
<Button block variant="secondary" onClick={onSecondaryAction} aria-label={secondaryActionLabel}>
{secondaryActionLabel}
</Button>
)}
{showClose && (
<Button variant="text" onClick={onClose} aria-label={closeLabel}>
{closeLabel}
</Button>
)}
</div>
</div>
</div>
</div>
</Transition.Child>
</Dialog>
</Transition>
);
};
</Transition.Child>
</Dialog>
</Transition>
);
},
);

ActionHalfModal.displayName = 'ActionHalfModal';
Loading