Skip to content

Commit

Permalink
feat(label): remove hardcoded strings for i18n (#2274)
Browse files Browse the repository at this point in the history
* feat: remove hardcoded string

* chore: add changeset

* feat(label): add i18nRequiredLabel prop

* feat(inline-control-group): pass i18nRequiredLabel to the legend

* feat(checkbox): add i18nRequiredLabel prop to CheckboxGroup

* feat(radio): add i18nRequiredLabel prop to RadioGroup

* chore: add changeset

* feat: add i18n examples to site

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
shleewhite and kodiakhq[bot] committed Mar 9, 2022
1 parent a6fc874 commit 554da97
Show file tree
Hide file tree
Showing 17 changed files with 300 additions and 62 deletions.
6 changes: 6 additions & 0 deletions .changeset/cuddly-students-agree.md
@@ -0,0 +1,6 @@
---
'@twilio-paste/label': patch
'@twilio-paste/core': patch
---

[Label] add i18nRequiredLabel prop for i18n
8 changes: 8 additions & 0 deletions .changeset/great-fans-decide.md
@@ -0,0 +1,8 @@
---
'@twilio-paste/checkbox': patch
'@twilio-paste/inline-control-group': patch
'@twilio-paste/radio-group': patch
'@twilio-paste/core': patch
---

[checkbox, inline-control-group, radio-group] add i18nRequiredLabel prop for i18n
Expand Up @@ -75,12 +75,15 @@ describe('Checkbox', () => {
});

it('should render a required dot', () => {
const {getByText} = render(
render(
<Checkbox {...defaultProps} required onChange={NOOP}>
foo
</Checkbox>
);
expect(getByText('Required:')).not.toBeNull();
const label = screen.getByText('foo');
const requiredDot = label.querySelector('[data-paste-element="REQUIRED_DOT"]');

expect(requiredDot).toBeDefined();
});

it('should render as indeterminate', () => {
Expand Down Expand Up @@ -153,12 +156,16 @@ describe('Checkbox Group', () => {
});

it('should have a required a required dot in the legend', () => {
const {getByText} = render(
render(
<CheckboxGroup {...defaultGroupProps} required>
<Checkbox {...defaultProps}>foo</Checkbox>
</CheckboxGroup>
);
expect(getByText('Required:')).not.toBeNull();

const fieldset = screen.getByRole('group');
const requiredDot = fieldset.querySelector('[data-paste-element="LEGEND_REQUIRED_DOT"]');

expect(requiredDot).toBeDefined();
});

it('should render a name', () => {
Expand Down Expand Up @@ -420,3 +427,31 @@ describe('Accessibility', () => {
expect(results).toHaveNoViolations();
});
});

describe('i18n', () => {
it('Should have default text for the required dot in the legend', () => {
render(
<CheckboxGroup {...defaultGroupProps} required>
<Checkbox {...defaultProps}>foo</Checkbox>
</CheckboxGroup>
);

const fieldset = screen.getByRole('group');
const requiredDot = fieldset.querySelector('[data-paste-element="LEGEND_REQUIRED_DOT"]');

expect(requiredDot?.textContent).toEqual('(required)');
});

it('Should use the i18nRequiredLabel prop for the required dot in the legend', () => {
render(
<CheckboxGroup {...defaultGroupProps} required i18nRequiredLabel="(requis)">
<Checkbox {...defaultProps}>foo</Checkbox>
</CheckboxGroup>
);

const fieldset = screen.getByRole('group');
const requiredDot = fieldset.querySelector('[data-paste-element="LEGEND_REQUIRED_DOT"]');

expect(requiredDot?.textContent).toEqual('(requis)');
});
});
Expand Up @@ -25,7 +25,11 @@ describe('Checkbox Disclaimer', () => {
foo
</CheckboxDisclaimer>
);
expect(getByText('Required:')).not.toBeNull();

const label = getByText('foo');
const requiredDot = label.querySelector('[data-paste-element="REQUIRED_DOT"]');

expect(requiredDot).toBeDefined();
});

it('renders a errorText message when errorText prop is present', () => {
Expand Down
4 changes: 4 additions & 0 deletions packages/paste-core/components/checkbox/src/CheckboxGroup.tsx
Expand Up @@ -8,6 +8,7 @@ export interface CheckboxGroupProps extends InlineControlGroupProps {
isSelectAll?: boolean;
name: string;
onChange?: (checked: boolean) => void;
i18nRequiredLabel?: string;
}

const CheckboxGroup = React.forwardRef<HTMLFieldSetElement, CheckboxGroupProps>(
Expand All @@ -21,6 +22,7 @@ const CheckboxGroup = React.forwardRef<HTMLFieldSetElement, CheckboxGroupProps>(
name,
onChange,
orientation = 'vertical',
i18nRequiredLabel = '(required)',
...props
},
ref
Expand Down Expand Up @@ -53,6 +55,7 @@ const CheckboxGroup = React.forwardRef<HTMLFieldSetElement, CheckboxGroupProps>(
name={name}
orientation={orientation}
ref={ref}
i18nRequiredLabel={i18nRequiredLabel}
>
{React.Children.map(children, (child, index) => {
return React.isValidElement(child)
Expand Down Expand Up @@ -80,6 +83,7 @@ if (process.env.NODE_ENV === 'development') {
errorText: PropTypes.string,
helpText: PropTypes.string,
orientation: PropTypes.oneOf(['vertical', 'horizontal']),
i18nRequiredLabel: PropTypes.string,
};
}

Expand Down
Expand Up @@ -30,12 +30,16 @@ describe('InlineControlGroup', () => {
});

it('should have a required a required dot in the legend', () => {
const {getByText} = testRender(
testRender(
<InlineControlGroup {...defaultGroupProps} required>
<div />
</InlineControlGroup>
);
expect(getByText('Required:')).not.toBeNull();

const fieldset = screen.getByRole('group');
const requiredDot = fieldset.querySelector('[data-paste-element="LEGEND_REQUIRED_DOT"]');

expect(requiredDot).toBeDefined();
});

it('renders a helpText message when helpText prop is present', () => {
Expand Down
Expand Up @@ -16,6 +16,7 @@ export interface InlineControlGroupProps
orientation?: 'vertical' | 'horizontal';
ref?: any;
required?: boolean;
i18nRequiredLabel?: string;
}

const InlineControlGroup = React.forwardRef<HTMLFieldSetElement, InlineControlGroupProps>(
Expand All @@ -29,6 +30,7 @@ const InlineControlGroup = React.forwardRef<HTMLFieldSetElement, InlineControlGr
legend,
orientation = 'vertical',
required,
i18nRequiredLabel,
...props
},
ref
Expand All @@ -51,6 +53,7 @@ const InlineControlGroup = React.forwardRef<HTMLFieldSetElement, InlineControlGr
required={required}
marginBottom="space0"
disabled={disabled}
i18nRequiredLabel={i18nRequiredLabel}
>
{legend}
</Label>
Expand Down Expand Up @@ -91,6 +94,7 @@ if (process.env.NODE_ENV === 'development') {
name: PropTypes.string.isRequired,
orientation: PropTypes.oneOf(['vertical', 'horizontal']),
required: PropTypes.bool,
i18nRequiredLabel: PropTypes.string,
};
}

Expand Down
27 changes: 24 additions & 3 deletions packages/paste-core/components/label/__tests__/label.test.tsx
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import {render, screen} from '@testing-library/react';

import {Customized} from '../stories/label.stories';
import {Label} from '../src';
import {Label, RequiredDot} from '../src';

describe('Label for prop', () => {
const initialProps = {
Expand All @@ -26,8 +26,15 @@ describe('Label required prop', () => {
};

it('should have a required indicator', () => {
render(<Label {...initialProps}>label</Label>);
expect(screen.getByText('Required:')).not.toBeNull();
render(
<Label data-testid="test-label" {...initialProps}>
label
</Label>
);
const label = screen.getByTestId('test-label');
const requiredDot = label.querySelector('[data-paste-element="REQUIRED_DOT"]');

expect(requiredDot).toBeDefined();
});
});

Expand Down Expand Up @@ -66,3 +73,17 @@ describe('Customization', () => {
expect(customRequiredDotWrapper).toHaveStyleRule('cursor', 'help');
});
});

describe('RequiredDot', () => {
it('should not have text by default', () => {
render(<RequiredDot data-testid="test-dot" />);
const dot = screen.getByTestId('test-dot');
expect(dot?.textContent).toEqual('');
});

it('should use i18nLabel prop for the dot text', () => {
render(<RequiredDot data-testid="test-dot" i18nLabel="(required)" />);
const dot = screen.getByTestId('test-dot');
expect(dot?.textContent).toEqual('(required)');
});
});
19 changes: 17 additions & 2 deletions packages/paste-core/components/label/src/Label.tsx
Expand Up @@ -15,10 +15,24 @@ export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement>,
marginBottom?: 'space0';
required?: boolean;
variant?: LabelVariants;
i18nRequiredLabel?: string;
}

const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
({as = 'label', marginBottom, required, disabled, children, variant, element = 'LABEL', ...props}, ref) => {
(
{
as = 'label',
marginBottom,
required,
disabled,
children,
variant,
element = 'LABEL',
i18nRequiredLabel = '',
...props
},
ref
) => {
let textColor = 'colorText' as TextColor;
if (disabled && variant === 'inverse') {
textColor = 'colorTextInverseWeak';
Expand Down Expand Up @@ -58,7 +72,7 @@ const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
<MediaObject verticalAlign="top">
{required && (
<MediaFigure spacing="space20">
<RequiredDot element={`${element}_REQUIRED_DOT`} />
<RequiredDot element={`${element}_REQUIRED_DOT`} i18nLabel={i18nRequiredLabel} />
</MediaFigure>
)}
<MediaBody>{children}</MediaBody>
Expand All @@ -77,6 +91,7 @@ Label.propTypes = {
htmlFor: PropTypes.string,
marginBottom: PropTypes.oneOf(['space0']),
required: PropTypes.bool,
i18nRequiredLabel: PropTypes.string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
variant: PropTypes.oneOf(['default', 'inverse']) as any,
};
Expand Down
5 changes: 3 additions & 2 deletions packages/paste-core/components/label/src/RequiredDot.tsx
Expand Up @@ -6,9 +6,10 @@ import {ScreenReaderOnly} from '@twilio-paste/screen-reader-only';

export interface RequiredDotProps {
element?: BoxProps['element'];
i18nLabel?: string;
}

export const RequiredDot: React.FC<RequiredDotProps> = ({element = 'REQUIRED_DOT', ...props}) => {
export const RequiredDot: React.FC<RequiredDotProps> = ({element = 'REQUIRED_DOT', i18nLabel = '', ...props}) => {
return (
<Box
{...safelySpreadBoxProps(props)}
Expand All @@ -29,7 +30,7 @@ export const RequiredDot: React.FC<RequiredDotProps> = ({element = 'REQUIRED_DOT
width="4px"
element={element}
>
<ScreenReaderOnly>Required: </ScreenReaderOnly>
<ScreenReaderOnly>{i18nLabel}</ScreenReaderOnly>
</Box>
</Box>
);
Expand Down
12 changes: 12 additions & 0 deletions packages/paste-core/components/label/stories/label.stories.tsx
Expand Up @@ -49,6 +49,18 @@ export const Inverse = (): React.ReactNode => {
);
};

export const I18n = (): React.ReactNode => {
return (
<>
<Label htmlFor="label" required i18nRequiredLabel="(requis)">
Prénom
</Label>
</>
);
};

I18n.storyName = 'i18n label';

export const Customized: React.FC = () => {
return (
<Stack orientation="vertical" spacing="space20">
Expand Down
@@ -1,5 +1,5 @@
import * as React from 'react';
import {render, fireEvent} from '@testing-library/react';
import {screen, render, fireEvent} from '@testing-library/react';
import {CustomizationProvider} from '@twilio-paste/customization';
import type {PasteCustomCSS} from '@twilio-paste/customization';
import {matchers} from 'jest-emotion';
Expand Down Expand Up @@ -127,12 +127,16 @@ describe('Radio Group', () => {
});

it('should have a required a required dot in the legend', () => {
const {getByText} = render(
render(
<RadioGroup {...defaultGroupProps} required>
<Radio {...defaultProps}>foo</Radio>
</RadioGroup>
);
expect(getByText('Required:')).not.toBeNull();

const fieldset = screen.getByRole('group');
const requiredDot = fieldset.querySelector('[data-paste-element="LEGEND_REQUIRED_DOT"]');

expect(requiredDot).toBeDefined();
});

it('should render a value', () => {
Expand Down Expand Up @@ -172,6 +176,34 @@ describe('Radio Group', () => {
);
expect(getByText(errorText)).toBeDefined();
});

describe('i18n', () => {
it('Should have default text for the required dot in the legend', () => {
render(
<RadioGroup {...defaultGroupProps} required>
<Radio {...defaultProps}>foo</Radio>
</RadioGroup>
);

const fieldset = screen.getByRole('group');
const requiredDot = fieldset.querySelector('[data-paste-element="LEGEND_REQUIRED_DOT"]');

expect(requiredDot?.textContent).toEqual('(required)');
});

it('Should use the i18nRequiredLabel prop for the required dot in the legend', () => {
render(
<RadioGroup {...defaultGroupProps} required i18nRequiredLabel="(requis)">
<Radio {...defaultProps}>foo</Radio>
</RadioGroup>
);

const fieldset = screen.getByRole('group');
const requiredDot = fieldset.querySelector('[data-paste-element="LEGEND_REQUIRED_DOT"]');

expect(requiredDot?.textContent).toEqual('(requis)');
});
});
});

describe('Radio event handlers', () => {
Expand Down

0 comments on commit 554da97

Please sign in to comment.