@boostdev/design-system-react-native 0.4.0 → 0.5.1

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 (67) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +10 -0
  3. package/dist/index.cjs +854 -764
  4. package/dist/index.d.cts +35 -4
  5. package/dist/index.d.ts +35 -4
  6. package/dist/index.js +645 -659
  7. package/package.json +1 -1
  8. package/src/components/interaction/Button/Button.mdx +5 -1
  9. package/src/components/interaction/Button/Button.spec.tsx +40 -0
  10. package/src/components/interaction/Button/Button.tsx +15 -8
  11. package/src/components/interaction/Dialog/Dialog.tsx +2 -9
  12. package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +2 -13
  13. package/src/components/interaction/Rating/Rating.tsx +2 -1
  14. package/src/components/interaction/SideSheet/SideSheet.tsx +2 -11
  15. package/src/components/interaction/Toast/Toast.tsx +2 -9
  16. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +2 -1
  17. package/src/components/interaction/form/Combobox/Combobox.spec.tsx +9 -0
  18. package/src/components/interaction/form/Combobox/Combobox.tsx +4 -12
  19. package/src/components/interaction/form/FileInput/FileInput.spec.tsx +6 -0
  20. package/src/components/interaction/form/FileInput/FileInput.tsx +3 -2
  21. package/src/components/interaction/form/FormInput/FormInput.spec.tsx +18 -9
  22. package/src/components/interaction/form/FormInput/FormInput.tsx +5 -11
  23. package/src/components/interaction/form/NumberInput/NumberInput.spec.tsx +8 -0
  24. package/src/components/interaction/form/NumberInput/NumberInput.tsx +6 -13
  25. package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +2 -1
  26. package/src/components/interaction/form/SegmentedControl/SegmentedControl.spec.tsx +40 -0
  27. package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +8 -2
  28. package/src/components/interaction/form/Select/Select.mdx +2 -1
  29. package/src/components/interaction/form/Select/Select.spec.tsx +22 -1
  30. package/src/components/interaction/form/Select/Select.tsx +9 -17
  31. package/src/components/interaction/form/Slider/Slider.tsx +2 -9
  32. package/src/components/interaction/form/Textarea/Textarea.spec.tsx +6 -0
  33. package/src/components/interaction/form/Textarea/Textarea.tsx +3 -8
  34. package/src/components/interaction/form/atoms/Label.tsx +2 -1
  35. package/src/components/interaction/form/atoms/Message.tsx +2 -1
  36. package/src/components/layout/SectionHeader/SectionHeader.tsx +2 -1
  37. package/src/components/ui/Accordion/Accordion.tsx +2 -11
  38. package/src/components/ui/Alert/Alert.tsx +2 -1
  39. package/src/components/ui/Avatar/Avatar.tsx +2 -1
  40. package/src/components/ui/Badge/Badge.spec.tsx +28 -0
  41. package/src/components/ui/Badge/Badge.tsx +9 -3
  42. package/src/components/ui/Breadcrumb/Breadcrumb.tsx +2 -1
  43. package/src/components/ui/Calendar/Calendar.tsx +2 -8
  44. package/src/components/ui/Carousel/Carousel.tsx +2 -12
  45. package/src/components/ui/Collapsible/Collapsible.spec.tsx +1 -1
  46. package/src/components/ui/Collapsible/Collapsible.tsx +7 -22
  47. package/src/components/ui/DescriptionList/DescriptionList.tsx +2 -1
  48. package/src/components/ui/Link/Link.tsx +2 -1
  49. package/src/components/ui/NotificationBanner/NotificationBanner.tsx +2 -1
  50. package/src/components/ui/Pagination/Pagination.tsx +2 -1
  51. package/src/components/ui/Progress/Progress.tsx +2 -1
  52. package/src/components/ui/ProgressCircle/ProgressCircle.tsx +2 -1
  53. package/src/components/ui/Table/Table.tsx +2 -9
  54. package/src/components/ui/Tabs/Tabs.tsx +2 -10
  55. package/src/components/ui/Tooltip/Tooltip.tsx +2 -1
  56. package/src/components/ui/Typography/Typography.mdx +2 -0
  57. package/src/components/ui/Typography/Typography.spec.tsx +59 -0
  58. package/src/components/ui/Typography/Typography.tsx +18 -2
  59. package/src/index.ts +2 -0
  60. package/src/native/Chevron.spec.tsx +34 -0
  61. package/src/native/Chevron.tsx +50 -0
  62. package/src/native/ScaledText.guard.spec.ts +25 -0
  63. package/src/native/ScaledText.spec.tsx +77 -0
  64. package/src/native/ScaledText.tsx +30 -0
  65. package/src/native/fontScale.spec.ts +35 -0
  66. package/src/native/fontScale.ts +15 -0
  67. package/src/test/cornerRadii.ts +15 -0
@@ -0,0 +1,25 @@
1
+ import { readFileSync, readdirSync, statSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ // Not `new URL('../components', import.meta.url)`: Vite statically matches that exact pattern as
6
+ // an asset-URL import and resolves it against the dev server's location, which under jsdom is a
7
+ // fake `http://localhost:3000/`, not the real file. Building the path from `dirname` avoids it.
8
+ const componentsDir = join(dirname(fileURLToPath(import.meta.url)), '../components');
9
+
10
+ function sourceFiles(dir: string): string[] {
11
+ return readdirSync(dir).flatMap((name) => {
12
+ const path = join(dir, name);
13
+ if (statSync(path).isDirectory()) return sourceFiles(path);
14
+ return /\.tsx?$/.test(name) && !/\.(spec|stories)\.tsx?$/.test(name) ? [path] : [];
15
+ });
16
+ }
17
+
18
+ describe('text in components', () => {
19
+ it('never takes Text or TextInput from react-native directly', () => {
20
+ const offenders = sourceFiles(componentsDir).filter((file) =>
21
+ /import\s*{[^}]*\b(Text|TextInput)\b[^}]*}\s*from\s*'react-native'/.test(readFileSync(file, 'utf8')),
22
+ );
23
+ expect(offenders).toEqual([]);
24
+ });
25
+ });
@@ -0,0 +1,77 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import type React from 'react';
3
+ import { maxFontScaleForStyle, ScaledText, ScaledTextInput } from './ScaledText';
4
+
5
+ describe('maxFontScaleForStyle', () => {
6
+ it('returns 2 for a fontSize of 16', () => {
7
+ expect(maxFontScaleForStyle({ fontSize: 16 })).toBe(2);
8
+ });
9
+
10
+ it('returns 1.3 for a style array, the later style wins', () => {
11
+ expect(maxFontScaleForStyle([{ fontSize: 16 }, { fontSize: 28 }])).toBe(1.3);
12
+ });
13
+
14
+ it('returns 2 for an undefined style', () => {
15
+ expect(maxFontScaleForStyle(undefined)).toBe(2);
16
+ });
17
+
18
+ it('returns the override when given', () => {
19
+ expect(maxFontScaleForStyle({ fontSize: 16 }, 1)).toBe(1);
20
+ });
21
+
22
+ it('returns the computed value when the override is null', () => {
23
+ expect(maxFontScaleForStyle({ fontSize: 16 }, null)).toBe(2);
24
+ });
25
+ });
26
+
27
+ vi.mock('react-native', async (importOriginal) => {
28
+ const actual = await importOriginal<typeof import('react-native')>();
29
+ return {
30
+ ...actual,
31
+ Text: ({
32
+ children,
33
+ maxFontSizeMultiplier,
34
+ numberOfLines,
35
+ adjustsFontSizeToFit,
36
+ minimumFontScale,
37
+ }: Record<string, unknown> & { children?: React.ReactNode }) => (
38
+ <span
39
+ data-testid="text"
40
+ data-max-font-scale={String(maxFontSizeMultiplier)}
41
+ data-lines={String(numberOfLines)}
42
+ data-fit={String(adjustsFontSizeToFit)}
43
+ data-min-scale={String(minimumFontScale)}
44
+ >
45
+ {children}
46
+ </span>
47
+ ),
48
+ TextInput: ({
49
+ maxFontSizeMultiplier,
50
+ }: Record<string, unknown> & { children?: React.ReactNode }) => (
51
+ <input data-testid="text-input" data-max-font-scale={String(maxFontSizeMultiplier)} />
52
+ ),
53
+ };
54
+ });
55
+
56
+ describe('ScaledText', () => {
57
+ it('caps a large figure at a multiplier of 1', () => {
58
+ render(<ScaledText style={{ fontSize: 88 }}>7:00</ScaledText>);
59
+ expect(screen.getByTestId('text')).toHaveAttribute('data-max-font-scale', '1');
60
+ });
61
+
62
+ it('uses the override multiplier when given', () => {
63
+ render(
64
+ <ScaledText style={{ fontSize: 16 }} maxFontSizeMultiplier={1}>
65
+ Body
66
+ </ScaledText>,
67
+ );
68
+ expect(screen.getByTestId('text')).toHaveAttribute('data-max-font-scale', '1');
69
+ });
70
+ });
71
+
72
+ describe('ScaledTextInput', () => {
73
+ it('caps a body input at a multiplier of 2', () => {
74
+ render(<ScaledTextInput style={{ fontSize: 16 }} />);
75
+ expect(screen.getByTestId('text-input')).toHaveAttribute('data-max-font-scale', '2');
76
+ });
77
+ });
@@ -0,0 +1,30 @@
1
+ import { forwardRef } from 'react';
2
+ import { StyleSheet, Text, TextInput } from 'react-native';
3
+ import type { StyleProp, TextInputProps, TextProps, TextStyle } from 'react-native';
4
+ import { DEFAULT_FONT_SIZE, maxFontScaleFor } from './fontScale';
5
+
6
+ /** The ceiling for a text with this style (see `maxFontScaleFor`), unless the caller passes its own. */
7
+ export function maxFontScaleForStyle(style: StyleProp<TextStyle>, override?: number | null): number {
8
+ if (override !== undefined && override !== null) return override;
9
+ const fontSize = StyleSheet.flatten(style)?.fontSize;
10
+ return maxFontScaleFor(typeof fontSize === 'number' ? fontSize : DEFAULT_FONT_SIZE);
11
+ }
12
+
13
+ /**
14
+ * React Native's `Text` with a ceiling on how far it grows with the OS text size, taken from its
15
+ * own font size. Pass `maxFontSizeMultiplier` to override it, e.g. `1` for text in a fixed shape.
16
+ */
17
+ export const ScaledText = forwardRef<Text, TextProps>(function ScaledText(
18
+ { maxFontSizeMultiplier, style, ...rest },
19
+ ref,
20
+ ) {
21
+ return <Text ref={ref} style={style} maxFontSizeMultiplier={maxFontScaleForStyle(style, maxFontSizeMultiplier)} {...rest} />;
22
+ });
23
+
24
+ /** React Native's `TextInput` with the same ceiling as `ScaledText`. */
25
+ export const ScaledTextInput = forwardRef<TextInput, TextInputProps>(function ScaledTextInput(
26
+ { maxFontSizeMultiplier, style, ...rest },
27
+ ref,
28
+ ) {
29
+ return <TextInput ref={ref} style={style} maxFontSizeMultiplier={maxFontScaleForStyle(style, maxFontSizeMultiplier)} {...rest} />;
30
+ });
@@ -0,0 +1,35 @@
1
+ import { maxFontScaleFor } from './fontScale';
2
+
3
+ describe('maxFontScaleFor', () => {
4
+ it('returns 2 for a font size of 13', () => {
5
+ expect(maxFontScaleFor(13)).toBe(2);
6
+ });
7
+
8
+ it('returns 2 for a font size of 19.5', () => {
9
+ expect(maxFontScaleFor(19.5)).toBe(2);
10
+ });
11
+
12
+ it('returns 1.5 for a font size of 20', () => {
13
+ expect(maxFontScaleFor(20)).toBe(1.5);
14
+ });
15
+
16
+ it('returns 1.5 for a font size of 25', () => {
17
+ expect(maxFontScaleFor(25)).toBe(1.5);
18
+ });
19
+
20
+ it('returns 1.3 for a font size of 26', () => {
21
+ expect(maxFontScaleFor(26)).toBe(1.3);
22
+ });
23
+
24
+ it('returns 1.3 for a font size of 59', () => {
25
+ expect(maxFontScaleFor(59)).toBe(1.3);
26
+ });
27
+
28
+ it('returns 1 for a font size of 60', () => {
29
+ expect(maxFontScaleFor(60)).toBe(1);
30
+ });
31
+
32
+ it('returns 1 for a font size of 112', () => {
33
+ expect(maxFontScaleFor(112)).toBe(1);
34
+ });
35
+ });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * How far text of this size may grow with the OS text size (iOS Dynamic Type, Android font
3
+ * scale), as React Native's `maxFontSizeMultiplier`. Big text grows less: a 16 pt body line may
4
+ * double, a 32 pt heading grows to about 42, and a figure of 60 pt or more (a countdown, a score)
5
+ * stays as it is.
6
+ */
7
+ export function maxFontScaleFor(fontSize: number): number {
8
+ if (fontSize >= 60) return 1;
9
+ if (fontSize >= 26) return 1.3;
10
+ if (fontSize >= 20) return 1.5;
11
+ return 2;
12
+ }
13
+
14
+ /** React Native's own font size for a text whose style sets none. */
15
+ export const DEFAULT_FONT_SIZE = 14;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The four corner radii of an element, clockwise from top-left.
3
+ *
4
+ * react-native-web expands `borderRadius` into the four corner longhands, and jsdom
5
+ * does not rebuild the `border-radius` shorthand from them, so a spec reads the corners.
6
+ */
7
+ export function cornerRadii(element: Element): string[] {
8
+ const style = getComputedStyle(element);
9
+ return [
10
+ style.borderTopLeftRadius,
11
+ style.borderTopRightRadius,
12
+ style.borderBottomRightRadius,
13
+ style.borderBottomLeftRadius,
14
+ ];
15
+ }