Skip to content

Commit c847703

Browse files
feat: add Caption Text component (#528)
1 parent ff900ff commit c847703

24 files changed

+236
-188
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { render, screen } from '@testing-library/react';
2+
import { describe, expect, test } from 'vitest';
3+
4+
import { DialCaptionText } from './CaptionText';
5+
6+
describe('Dial UI Kit :: DialCaptionText', () => {
7+
test('Should render text when provided', () => {
8+
render(<DialCaptionText text="This is an text" />);
9+
expect(screen.getByText('This is an text')).toBeInTheDocument();
10+
});
11+
12+
test('Should render nothing when text is not provided', () => {
13+
const { container } = render(<DialCaptionText />);
14+
expect(container).toBeEmptyDOMElement();
15+
});
16+
17+
test('Should pass through span props and merge className', () => {
18+
render(
19+
<DialCaptionText
20+
text="With extra props"
21+
className="extra-class"
22+
aria-label="caption-label"
23+
/>,
24+
);
25+
26+
const el = screen.getByText('With extra props');
27+
28+
expect(el).toBeInTheDocument();
29+
expect(el).toHaveTextContent('With extra props');
30+
expect(el).toHaveAttribute('aria-label', 'caption-label');
31+
expect(el.className).toContain('dial-tiny-text');
32+
expect(el.className).toContain('extra-class');
33+
});
34+
35+
test('Should pass through span props and merge className', () => {
36+
render(
37+
<DialCaptionText
38+
text="With extra props"
39+
className="extra-class"
40+
aria-label="caption-label"
41+
/>,
42+
);
43+
44+
const el = screen.getByText('With extra props');
45+
46+
expect(el).toBeInTheDocument();
47+
expect(el).toHaveTextContent('With extra props');
48+
expect(el).toHaveAttribute('aria-label', 'caption-label');
49+
expect(el.className).toContain('dial-tiny-text');
50+
expect(el.className).toContain('extra-class');
51+
});
52+
});
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import type { Meta, StoryObj } from '@storybook/react-vite';
2+
import { CaptionType } from '@/types/caption';
3+
import { DialCaptionText } from './CaptionText';
4+
5+
const meta = {
6+
title: 'DIAL/Elements/CaptionText',
7+
component: DialCaptionText,
8+
parameters: {
9+
layout: 'centered',
10+
docs: {
11+
description: {
12+
component:
13+
'A component for displaying messages with consistent styling.',
14+
},
15+
},
16+
},
17+
argTypes: {
18+
variant: {
19+
control: { type: 'select' },
20+
options: [CaptionType.Error, CaptionType.Description],
21+
description: 'The visual style variant of the caption text.',
22+
},
23+
text: {
24+
control: { type: 'text' },
25+
description: 'The message text to display',
26+
},
27+
className: {
28+
control: { type: 'text' },
29+
description: 'Additional CSS classes merged with base classes.',
30+
},
31+
'aria-label': {
32+
control: { type: 'text' },
33+
description: 'ARIA label to improve accessibility.',
34+
},
35+
},
36+
args: {
37+
text: 'Field is required. Please provide a valid input to proceed.',
38+
},
39+
} satisfies Meta<typeof DialCaptionText>;
40+
41+
export default meta;
42+
type Story = StoryObj<typeof meta>;
43+
44+
export const Default: Story = {};
45+
46+
export const CustomClassName: Story = {
47+
args: {
48+
text: 'This is an error with custom class',
49+
className: 'border border-secondary p-2',
50+
},
51+
};
52+
53+
export const AriaAttributes: Story = {
54+
args: {
55+
text: 'Error with aria attributes',
56+
'aria-label': 'Error message',
57+
},
58+
};
59+
60+
export const Error: Story = {
61+
args: {
62+
variant: CaptionType.Error,
63+
},
64+
};
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { mergeClasses } from '@/utils/merge-classes';
2+
import type { FC, HTMLAttributes } from 'react';
3+
import { CaptionType } from '@/types/caption';
4+
5+
export interface DialCaptionTextProps extends HTMLAttributes<HTMLSpanElement> {
6+
text?: string;
7+
variant?: CaptionType;
8+
}
9+
10+
/**
11+
* A component for displaying error messages with consistent styling
12+
*
13+
* @example
14+
* ```tsx
15+
* <DialCaptionText text="This field is required" variant={CaptionType.Error} />
16+
* ```
17+
*
18+
* @param [text] - The text to display. If undefined or empty, nothing is rendered
19+
* @param [variant] - The variant of the caption text, e.g., error or description.
20+
*/
21+
export const DialCaptionText: FC<DialCaptionTextProps> = ({
22+
text,
23+
className,
24+
variant = CaptionType.Description,
25+
...props
26+
}) => {
27+
if (!text) return null;
28+
29+
return (
30+
<span
31+
{...props}
32+
role="alert"
33+
className={mergeClasses(
34+
'dial-tiny-text',
35+
variant === CaptionType.Error ? 'text-error' : 'text-secondary',
36+
className,
37+
)}
38+
>
39+
{text}
40+
</span>
41+
);
42+
};
43+
44+
export const DialErrorText: FC<Omit<DialCaptionTextProps, 'variant'>> = ({
45+
...props
46+
}) => {
47+
return <DialCaptionText {...props} variant={CaptionType.Error} />;
48+
};

src/components/ErrorText/ErrorText.spec.tsx

Lines changed: 0 additions & 58 deletions
This file was deleted.

src/components/ErrorText/ErrorText.stories.tsx

Lines changed: 0 additions & 52 deletions
This file was deleted.

src/components/ErrorText/ErrorText.tsx

Lines changed: 0 additions & 34 deletions
This file was deleted.

src/components/FormItem/FormItem.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useCallback, type FC, type ReactNode } from 'react';
22

3-
import { DialErrorText } from '@/components/ErrorText/ErrorText';
3+
import { DialErrorText } from '@/components/CaptionText/CaptionText';
44

55
import { DialLabel } from '@/components/Label/Label';
66
import {
@@ -106,7 +106,7 @@ export const DialFormItem: FC<DialFormItemProps> = ({
106106
if (typeof error === 'string' || typeof error === 'undefined') {
107107
return error ? (
108108
<div id={errorId} aria-live="polite" className={errorClassName}>
109-
<DialErrorText errorText={error} />
109+
<DialErrorText text={error} />
110110
</div>
111111
) : null;
112112
}

src/components/InfoButton/InfoButton.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ export const DialInfoButton: FC<DialInfoButtonProps> = ({
3737
/>
3838
);
3939
return caption ? (
40-
<DialTooltip tooltip={caption} triggerClassName="flex">
40+
<DialTooltip
41+
tooltip={caption}
42+
triggerClassName="flex justify-center items-center"
43+
>
4144
{button}
4245
</DialTooltip>
4346
) : (
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
export const infoButtonClassName =
2-
'w-auto h-auto border border-solid rounded-[6px] border-transparent p-1 text-secondary hover:text-controls-accent-primary-hover active:text-controls-accent-primary-active focus-within:border-focus';
2+
'w-[24px] h-[24px] border border-solid rounded-[6px] border-transparent p-1 text-secondary hover:text-controls-accent-primary-hover active:text-controls-accent-primary-active focus-within:border-focus';

src/components/Input/Input.stories.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ export const MaxView: Story = {
134134
id="error-input"
135135
invalid={true}
136136
value="Text"
137-
errorText="Error message"
137+
error="Error message"
138138
{...props}
139139
/>
140140
</div>
@@ -236,7 +236,7 @@ export const AllVariants: Story = {
236236
placeholder="Placeholder"
237237
invalid={true}
238238
value="Text"
239-
errorText="Error message"
239+
error="Error message"
240240
iconBefore={<IconSearch size={16} />}
241241
iconAfter={<IconEye size={16} />}
242242
/>

0 commit comments

Comments
 (0)