uikit-react-public 0.48.0 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/components/ConfidentialDisplayField/ConfidentialDisplayField.d.ts +10 -0
  2. package/dist/components/ConfidentialDisplayField/ConfidentialDisplayField.stories.d.ts +19 -0
  3. package/dist/components/ConfidentialDisplayField/__tests__/ConfidentialDisplayField.test.d.ts +1 -0
  4. package/dist/components/ConfidentialDisplayField/index.d.ts +2 -0
  5. package/dist/components/ConfidentialInput/ConfidentialInput.d.ts +3 -1
  6. package/dist/components/ConfidentialInput/ConfidentialInput.stories.d.ts +1 -0
  7. package/dist/components/DisplayField/DisplayField.d.ts +11 -0
  8. package/dist/components/DisplayField/DisplayField.stories.d.ts +16 -0
  9. package/dist/components/DisplayField/__tests__/DisplayField.test.d.ts +1 -0
  10. package/dist/components/DisplayField/index.d.ts +2 -0
  11. package/dist/components/common/formatConfidentialValue.d.ts +3 -0
  12. package/dist/components/index.d.ts +4 -0
  13. package/dist/index.js +5210 -5112
  14. package/lib/components/ConfidentialDisplayField/ConfidentialDisplayField.stories.tsx +42 -0
  15. package/lib/components/ConfidentialDisplayField/ConfidentialDisplayField.tsx +103 -0
  16. package/lib/components/ConfidentialDisplayField/Documentation.mdx +43 -0
  17. package/lib/components/ConfidentialDisplayField/__tests__/ConfidentialDisplayField.test.tsx +125 -0
  18. package/lib/components/ConfidentialDisplayField/index.ts +2 -0
  19. package/lib/components/ConfidentialInput/ConfidentialInput.stories.tsx +6 -0
  20. package/lib/components/ConfidentialInput/ConfidentialInput.tsx +24 -39
  21. package/lib/components/ConfidentialInput/Documentation.mdx +12 -4
  22. package/lib/components/ConfidentialInput/__tests__/ConfidentialInput.test.tsx +76 -9
  23. package/lib/components/DisplayField/DisplayField.stories.tsx +26 -0
  24. package/lib/components/DisplayField/DisplayField.tsx +56 -0
  25. package/lib/components/DisplayField/Documentation.mdx +35 -0
  26. package/lib/components/DisplayField/__tests__/DisplayField.test.tsx +61 -0
  27. package/lib/components/DisplayField/index.ts +2 -0
  28. package/lib/components/common/formatConfidentialValue.ts +36 -0
  29. package/lib/components/index.ts +6 -0
  30. package/package.json +1 -1
@@ -0,0 +1,56 @@
1
+ import { forwardRef, memo, OutputHTMLAttributes, use } from 'react';
2
+ import { css, cx } from '@emotion/css';
3
+ import useTheme from '../../theme/useTheme';
4
+ import { FieldContext } from '../Field';
5
+ import marginsStyle, { MarginProps } from '../common/marginsStyle';
6
+
7
+ export const NAME = 'ucl-uikit-display-field';
8
+
9
+ export interface DisplayFieldBaseProps extends Omit<
10
+ OutputHTMLAttributes<HTMLOutputElement>,
11
+ 'children' | 'value'
12
+ > {
13
+ value: string | number;
14
+ testId?: string;
15
+ }
16
+
17
+ export type DisplayFieldProps = DisplayFieldBaseProps & MarginProps;
18
+
19
+ export type Ref = HTMLOutputElement;
20
+
21
+ const DisplayField = forwardRef<Ref, DisplayFieldProps>(
22
+ ({ value, testId = NAME, className, ...props }, ref) => {
23
+ const [theme] = useTheme();
24
+ const { id: contextId } = use(FieldContext);
25
+ const id = props.id ?? contextId;
26
+ const { md } = theme.typography.body;
27
+
28
+ const baseStyle = css`
29
+ display: block;
30
+ min-height: 24px;
31
+ color: ${theme.colour.text.default};
32
+ font-family: ${md.fontFamily};
33
+ font-feature-settings: ${md.fontSettings};
34
+ font-size: ${md.fontSize}px;
35
+ font-weight: ${md.fontWeight};
36
+ line-height: ${md.lineHeight}%;
37
+ overflow-wrap: anywhere;
38
+ `;
39
+
40
+ const style = cx(NAME, baseStyle, marginsStyle(props, theme), className);
41
+
42
+ return (
43
+ <output
44
+ {...props}
45
+ ref={ref}
46
+ id={id}
47
+ className={style}
48
+ data-testid={testId}
49
+ >
50
+ {value}
51
+ </output>
52
+ );
53
+ }
54
+ );
55
+
56
+ export default memo(DisplayField);
@@ -0,0 +1,35 @@
1
+ import * as DisplayFieldStories from './DisplayField.stories';
2
+ import {
3
+ Meta,
4
+ Title,
5
+ Subtitle,
6
+ Canvas,
7
+ Source,
8
+ ArgTypes,
9
+ } from '@storybook/addon-docs/blocks';
10
+
11
+ <Meta of={DisplayFieldStories} />
12
+ <Title />
13
+ <Subtitle>A read-only field for displaying a single value</Subtitle>
14
+
15
+ `DisplayField` renders a semantic HTML `<output>` element. Use it with `Label`
16
+ to display a record in the same field structure used by editable forms.
17
+
18
+ <Source
19
+ code={`<Field>
20
+ <Label>Account name</Label>
21
+ <DisplayField value='Personal savings' />
22
+ </Field>`}
23
+ />
24
+
25
+ ## Props
26
+
27
+ - `value`: **string | number** - The value to display.
28
+ - All standard HTML `<output>` props are supported.
29
+
30
+ <ArgTypes />
31
+
32
+ ## Examples
33
+
34
+ <Canvas of={DisplayFieldStories.Text} />
35
+ <Canvas of={DisplayFieldStories.Number} />
@@ -0,0 +1,61 @@
1
+ import { createRef } from 'react';
2
+ import { describe, expect, test } from 'vitest';
3
+ import { render, screen } from '@testing-library/react';
4
+ import { ThemeContextProvider } from '../../../theme/useTheme';
5
+ import DisplayField from '../DisplayField';
6
+ import Field from '../../Field';
7
+ import Label from '../../Label';
8
+
9
+ const renderDisplayField = (props: React.ComponentProps<typeof DisplayField>) =>
10
+ render(
11
+ <ThemeContextProvider>
12
+ <DisplayField {...props} />
13
+ </ThemeContextProvider>
14
+ );
15
+
16
+ describe('DisplayField', () => {
17
+ test('displays a text value', () => {
18
+ renderDisplayField({ value: 'Personal savings' });
19
+
20
+ expect(screen.getByTestId('ucl-uikit-display-field')).toHaveTextContent(
21
+ 'Personal savings'
22
+ );
23
+ });
24
+
25
+ test('displays a numeric value', () => {
26
+ renderDisplayField({ value: 12345678 });
27
+
28
+ expect(screen.getByTestId('ucl-uikit-display-field')).toHaveTextContent(
29
+ '12345678'
30
+ );
31
+ });
32
+
33
+ test('shares the Field id with its Label', () => {
34
+ render(
35
+ <ThemeContextProvider>
36
+ <Field>
37
+ <Label>Account name</Label>
38
+ <DisplayField value='Personal savings' />
39
+ </Field>
40
+ </ThemeContextProvider>
41
+ );
42
+
43
+ const label = screen.getByText('Account name');
44
+ const output = screen.getByTestId('ucl-uikit-display-field');
45
+ expect(label).toHaveAttribute('for', output.id);
46
+ });
47
+
48
+ test('forwards its ref to the output', () => {
49
+ const ref = createRef<HTMLOutputElement>();
50
+ render(
51
+ <ThemeContextProvider>
52
+ <DisplayField
53
+ ref={ref}
54
+ value='Personal savings'
55
+ />
56
+ </ThemeContextProvider>
57
+ );
58
+
59
+ expect(ref.current).toBe(screen.getByTestId('ucl-uikit-display-field'));
60
+ });
61
+ });
@@ -0,0 +1,2 @@
1
+ export { default } from './DisplayField';
2
+ export type { DisplayFieldProps } from './DisplayField';
@@ -0,0 +1,36 @@
1
+ export type MaskFormat = string | ((value: string) => string);
2
+
3
+ const applyMaskTemplate = (value: string, template: string) => {
4
+ const valueCharacters = Array.from(value);
5
+ let valueIndex = 0;
6
+ let maskedValue = '';
7
+
8
+ for (const templateCharacter of Array.from(template)) {
9
+ if (valueIndex >= valueCharacters.length) break;
10
+
11
+ if (templateCharacter === '1') {
12
+ maskedValue += valueCharacters[valueIndex];
13
+ valueIndex += 1;
14
+ } else if (templateCharacter === '*') {
15
+ maskedValue += '*';
16
+ valueIndex += 1;
17
+ } else {
18
+ maskedValue += templateCharacter;
19
+ }
20
+ }
21
+
22
+ return (
23
+ maskedValue + '*'.repeat(Math.max(0, valueCharacters.length - valueIndex))
24
+ );
25
+ };
26
+
27
+ const formatConfidentialValue = (value: string, maskFormat: MaskFormat) => {
28
+ const maskedValue =
29
+ typeof maskFormat === 'function'
30
+ ? maskFormat(value)
31
+ : applyMaskTemplate(value, maskFormat);
32
+
33
+ return maskedValue.split('*').join('•');
34
+ };
35
+
36
+ export default formatConfidentialValue;
@@ -7,6 +7,12 @@ export type { InputProps } from './Input';
7
7
  export { default as ConfidentialInput } from './ConfidentialInput';
8
8
  export type { ConfidentialInputProps } from './ConfidentialInput';
9
9
 
10
+ export { default as DisplayField } from './DisplayField';
11
+ export type { DisplayFieldProps } from './DisplayField';
12
+
13
+ export { default as ConfidentialDisplayField } from './ConfidentialDisplayField';
14
+ export type { ConfidentialDisplayFieldProps } from './ConfidentialDisplayField';
15
+
10
16
  export { default as Link } from './Link';
11
17
  export type { LinkProps } from './Link';
12
18
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "uikit-react-public",
3
3
  "private": false,
4
4
  "license": "UNLICENSED",
5
- "version": "0.48.0",
5
+ "version": "0.49.0",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
8
8
  "types": "dist/index.d.ts",