@react-xp/design-system 1.0.0-beta.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 (145) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +325 -0
  3. package/dist/cjs/helpers/a11y.js +176 -0
  4. package/dist/cjs/helpers/index.js +18 -0
  5. package/dist/cjs/helpers/styles.js +172 -0
  6. package/dist/cjs/index.js +19 -0
  7. package/dist/cjs/styles/avatars.js +63 -0
  8. package/dist/cjs/styles/badges.js +59 -0
  9. package/dist/cjs/styles/bottomSheets.js +76 -0
  10. package/dist/cjs/styles/buttons.js +129 -0
  11. package/dist/cjs/styles/cards.js +71 -0
  12. package/dist/cjs/styles/checkboxes.js +70 -0
  13. package/dist/cjs/styles/chips.js +98 -0
  14. package/dist/cjs/styles/datePickers.js +113 -0
  15. package/dist/cjs/styles/dividers.js +12 -0
  16. package/dist/cjs/styles/emptyStates.js +40 -0
  17. package/dist/cjs/styles/forms.js +32 -0
  18. package/dist/cjs/styles/headers.js +46 -0
  19. package/dist/cjs/styles/index.js +43 -0
  20. package/dist/cjs/styles/inputs.js +101 -0
  21. package/dist/cjs/styles/listItems.js +65 -0
  22. package/dist/cjs/styles/modals.js +92 -0
  23. package/dist/cjs/styles/pagination.js +39 -0
  24. package/dist/cjs/styles/popovers.js +64 -0
  25. package/dist/cjs/styles/progress.js +33 -0
  26. package/dist/cjs/styles/radios.js +70 -0
  27. package/dist/cjs/styles/selects.js +137 -0
  28. package/dist/cjs/styles/skeletons.js +34 -0
  29. package/dist/cjs/styles/steppers.js +71 -0
  30. package/dist/cjs/styles/switches.js +76 -0
  31. package/dist/cjs/styles/tables.js +57 -0
  32. package/dist/cjs/styles/tabs.js +71 -0
  33. package/dist/cjs/styles/toasts.js +82 -0
  34. package/dist/cjs/styles/tooltips.js +46 -0
  35. package/dist/cjs/types/index.js +18 -0
  36. package/dist/cjs/types/states.js +8 -0
  37. package/dist/cjs/types/tokens.js +18 -0
  38. package/dist/esm/helpers/a11y.js +169 -0
  39. package/dist/esm/helpers/index.js +2 -0
  40. package/dist/esm/helpers/styles.js +158 -0
  41. package/dist/esm/index.js +3 -0
  42. package/dist/esm/styles/avatars.js +59 -0
  43. package/dist/esm/styles/badges.js +55 -0
  44. package/dist/esm/styles/bottomSheets.js +72 -0
  45. package/dist/esm/styles/buttons.js +125 -0
  46. package/dist/esm/styles/cards.js +67 -0
  47. package/dist/esm/styles/checkboxes.js +66 -0
  48. package/dist/esm/styles/chips.js +94 -0
  49. package/dist/esm/styles/datePickers.js +108 -0
  50. package/dist/esm/styles/dividers.js +8 -0
  51. package/dist/esm/styles/emptyStates.js +36 -0
  52. package/dist/esm/styles/forms.js +28 -0
  53. package/dist/esm/styles/headers.js +42 -0
  54. package/dist/esm/styles/index.js +27 -0
  55. package/dist/esm/styles/inputs.js +97 -0
  56. package/dist/esm/styles/listItems.js +61 -0
  57. package/dist/esm/styles/modals.js +88 -0
  58. package/dist/esm/styles/pagination.js +35 -0
  59. package/dist/esm/styles/popovers.js +60 -0
  60. package/dist/esm/styles/progress.js +27 -0
  61. package/dist/esm/styles/radios.js +66 -0
  62. package/dist/esm/styles/selects.js +133 -0
  63. package/dist/esm/styles/skeletons.js +29 -0
  64. package/dist/esm/styles/steppers.js +66 -0
  65. package/dist/esm/styles/switches.js +72 -0
  66. package/dist/esm/styles/tables.js +53 -0
  67. package/dist/esm/styles/tabs.js +66 -0
  68. package/dist/esm/styles/toasts.js +77 -0
  69. package/dist/esm/styles/tooltips.js +42 -0
  70. package/dist/esm/types/index.js +2 -0
  71. package/dist/esm/types/states.js +3 -0
  72. package/dist/esm/types/tokens.js +12 -0
  73. package/dist/tsconfig.cjs.tsbuildinfo +1 -0
  74. package/dist/tsconfig.esm.tsbuildinfo +1 -0
  75. package/dist/types/helpers/a11y.d.ts +20 -0
  76. package/dist/types/helpers/a11y.d.ts.map +1 -0
  77. package/dist/types/helpers/index.d.ts +3 -0
  78. package/dist/types/helpers/index.d.ts.map +1 -0
  79. package/dist/types/helpers/styles.d.ts +31 -0
  80. package/dist/types/helpers/styles.d.ts.map +1 -0
  81. package/dist/types/index.d.ts +4 -0
  82. package/dist/types/index.d.ts.map +1 -0
  83. package/dist/types/styles/avatars.d.ts +10 -0
  84. package/dist/types/styles/avatars.d.ts.map +1 -0
  85. package/dist/types/styles/badges.d.ts +8 -0
  86. package/dist/types/styles/badges.d.ts.map +1 -0
  87. package/dist/types/styles/bottomSheets.d.ts +15 -0
  88. package/dist/types/styles/bottomSheets.d.ts.map +1 -0
  89. package/dist/types/styles/buttons.d.ts +22 -0
  90. package/dist/types/styles/buttons.d.ts.map +1 -0
  91. package/dist/types/styles/cards.d.ts +18 -0
  92. package/dist/types/styles/cards.d.ts.map +1 -0
  93. package/dist/types/styles/checkboxes.d.ts +18 -0
  94. package/dist/types/styles/checkboxes.d.ts.map +1 -0
  95. package/dist/types/styles/chips.d.ts +20 -0
  96. package/dist/types/styles/chips.d.ts.map +1 -0
  97. package/dist/types/styles/datePickers.d.ts +30 -0
  98. package/dist/types/styles/datePickers.d.ts.map +1 -0
  99. package/dist/types/styles/dividers.d.ts +4 -0
  100. package/dist/types/styles/dividers.d.ts.map +1 -0
  101. package/dist/types/styles/emptyStates.d.ts +10 -0
  102. package/dist/types/styles/emptyStates.d.ts.map +1 -0
  103. package/dist/types/styles/forms.d.ts +10 -0
  104. package/dist/types/styles/forms.d.ts.map +1 -0
  105. package/dist/types/styles/headers.d.ts +11 -0
  106. package/dist/types/styles/headers.d.ts.map +1 -0
  107. package/dist/types/styles/index.d.ts +28 -0
  108. package/dist/types/styles/index.d.ts.map +1 -0
  109. package/dist/types/styles/inputs.d.ts +19 -0
  110. package/dist/types/styles/inputs.d.ts.map +1 -0
  111. package/dist/types/styles/listItems.d.ts +19 -0
  112. package/dist/types/styles/listItems.d.ts.map +1 -0
  113. package/dist/types/styles/modals.d.ts +15 -0
  114. package/dist/types/styles/modals.d.ts.map +1 -0
  115. package/dist/types/styles/pagination.d.ts +9 -0
  116. package/dist/types/styles/pagination.d.ts.map +1 -0
  117. package/dist/types/styles/popovers.d.ts +12 -0
  118. package/dist/types/styles/popovers.d.ts.map +1 -0
  119. package/dist/types/styles/progress.d.ts +11 -0
  120. package/dist/types/styles/progress.d.ts.map +1 -0
  121. package/dist/types/styles/radios.d.ts +17 -0
  122. package/dist/types/styles/radios.d.ts.map +1 -0
  123. package/dist/types/styles/selects.d.ts +25 -0
  124. package/dist/types/styles/selects.d.ts.map +1 -0
  125. package/dist/types/styles/skeletons.d.ts +13 -0
  126. package/dist/types/styles/skeletons.d.ts.map +1 -0
  127. package/dist/types/styles/steppers.d.ts +21 -0
  128. package/dist/types/styles/steppers.d.ts.map +1 -0
  129. package/dist/types/styles/switches.d.ts +17 -0
  130. package/dist/types/styles/switches.d.ts.map +1 -0
  131. package/dist/types/styles/tables.d.ts +13 -0
  132. package/dist/types/styles/tables.d.ts.map +1 -0
  133. package/dist/types/styles/tabs.d.ts +19 -0
  134. package/dist/types/styles/tabs.d.ts.map +1 -0
  135. package/dist/types/styles/toasts.d.ts +13 -0
  136. package/dist/types/styles/toasts.d.ts.map +1 -0
  137. package/dist/types/styles/tooltips.d.ts +9 -0
  138. package/dist/types/styles/tooltips.d.ts.map +1 -0
  139. package/dist/types/types/index.d.ts +3 -0
  140. package/dist/types/types/index.d.ts.map +1 -0
  141. package/dist/types/types/states.d.ts +26 -0
  142. package/dist/types/types/states.d.ts.map +1 -0
  143. package/dist/types/types/tokens.d.ts +16 -0
  144. package/dist/types/types/tokens.d.ts.map +1 -0
  145. package/package.json +73 -0
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getToastViewportStyle = exports.getToastParts = void 0;
4
+ // styles/toasts.ts
5
+ const tokens_1 = require("../types/tokens");
6
+ const variantMap = (t) => ({
7
+ info: {
8
+ border: (0, tokens_1.tok)(t, 'color-status-info'),
9
+ icon: (0, tokens_1.tok)(t, 'color-status-info'),
10
+ },
11
+ success: {
12
+ border: (0, tokens_1.tok)(t, 'color-status-success'),
13
+ icon: (0, tokens_1.tok)(t, 'color-status-success'),
14
+ },
15
+ warning: {
16
+ border: (0, tokens_1.tok)(t, 'color-status-warning'),
17
+ icon: (0, tokens_1.tok)(t, 'color-status-warning'),
18
+ },
19
+ error: {
20
+ border: (0, tokens_1.tok)(t, 'color-status-error'),
21
+ icon: (0, tokens_1.tok)(t, 'color-status-error'),
22
+ },
23
+ });
24
+ const getToastParts = (t, variant = 'info') => {
25
+ const m = (0, tokens_1.motion)(t, 'motion-emphasis-enter');
26
+ const v = variantMap(t)[variant];
27
+ return {
28
+ container: {
29
+ zIndex: (0, tokens_1.tok)(t, 'z-index-layer-toast'),
30
+ backgroundColor: (0, tokens_1.tok)(t, 'color-surface'),
31
+ borderColor: v.border,
32
+ borderWidth: (0, tokens_1.tok)(t, 'border-width-default'),
33
+ borderRadius: (0, tokens_1.tok)(t, 'radius-4'),
34
+ shadow: (0, tokens_1.tok)(t, 'shadow-level-3'),
35
+ paddingVertical: (0, tokens_1.tok)(t, 'space-3'),
36
+ paddingHorizontal: (0, tokens_1.tok)(t, 'space-4'),
37
+ flexDirection: 'row',
38
+ gap: (0, tokens_1.tok)(t, 'space-3'),
39
+ alignItems: 'flex-start',
40
+ // semântico (runtime anima)
41
+ animationDurationMs: m.durationMs,
42
+ animationEasing: m.easing,
43
+ },
44
+ icon: {
45
+ width: (0, tokens_1.tok)(t, 'typography-font-size-5'),
46
+ height: (0, tokens_1.tok)(t, 'typography-font-size-5'),
47
+ color: v.icon,
48
+ },
49
+ content: { flex: 1, gap: (0, tokens_1.tok)(t, 'space-1') },
50
+ title: {
51
+ color: (0, tokens_1.tok)(t, 'color-fg-default'),
52
+ fontFamily: (0, tokens_1.tok)(t, 'typography-font-family-heading'),
53
+ fontSize: (0, tokens_1.tok)(t, 'typography-font-size-3'),
54
+ fontWeight: (0, tokens_1.tok)(t, 'typography-font-weight-strong'),
55
+ lineHeight: (0, tokens_1.tok)(t, 'typography-line-height-default'),
56
+ },
57
+ message: {
58
+ color: (0, tokens_1.tok)(t, 'color-fg-muted'),
59
+ fontFamily: (0, tokens_1.tok)(t, 'typography-font-family-base'),
60
+ fontSize: (0, tokens_1.tok)(t, 'typography-font-size-2'),
61
+ fontWeight: (0, tokens_1.tok)(t, 'typography-font-weight-regular'),
62
+ lineHeight: (0, tokens_1.tok)(t, 'typography-line-height-default'),
63
+ },
64
+ closeButton: {
65
+ minHeight: (0, tokens_1.tok)(t, 'tap-target-min'),
66
+ minWidth: (0, tokens_1.tok)(t, 'tap-target-min'),
67
+ alignItems: 'center',
68
+ justifyContent: 'center',
69
+ borderRadius: (0, tokens_1.tok)(t, 'radius-full'),
70
+ },
71
+ };
72
+ };
73
+ exports.getToastParts = getToastParts;
74
+ // opcional: estilos para “stack”/viewport de toasts
75
+ const getToastViewportStyle = (t) => ({
76
+ position: 'absolute',
77
+ right: (0, tokens_1.tok)(t, 'space-4'),
78
+ bottom: (0, tokens_1.tok)(t, 'space-4'),
79
+ gap: (0, tokens_1.tok)(t, 'space-2'),
80
+ zIndex: (0, tokens_1.tok)(t, 'z-index-layer-toast'),
81
+ });
82
+ exports.getToastViewportStyle = getToastViewportStyle;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getTooltipParts = void 0;
4
+ // styles/tooltips.ts
5
+ const tokens_1 = require("../types/tokens");
6
+ const getTooltipParts = (t) => {
7
+ const m = (0, tokens_1.motion)(t, 'motion-emphasis-enter');
8
+ return {
9
+ container: {
10
+ zIndex: (0, tokens_1.tok)(t, 'z-index-layer-dropdown'),
11
+ backgroundColor: (0, tokens_1.tok)(t, 'color-fg-default'), // inverted tooltip
12
+ borderColor: (0, tokens_1.tok)(t, 'color-fg-default'),
13
+ borderWidth: (0, tokens_1.tok)(t, 'border-width-default'),
14
+ borderRadius: (0, tokens_1.tok)(t, 'radius-3'),
15
+ shadow: (0, tokens_1.tok)(t, 'shadow-level-3'),
16
+ paddingVertical: (0, tokens_1.tok)(t, 'space-2'),
17
+ paddingHorizontal: (0, tokens_1.tok)(t, 'space-3'),
18
+ maxWidth: 280,
19
+ animationDurationMs: m.durationMs,
20
+ animationEasing: m.easing,
21
+ },
22
+ arrow: {
23
+ width: (0, tokens_1.tok)(t, 'space-2'),
24
+ height: (0, tokens_1.tok)(t, 'space-2'),
25
+ backgroundColor: (0, tokens_1.tok)(t, 'color-fg-default'),
26
+ // semântico: o runtime roda 45º e posiciona
27
+ transform: 'rotate(45deg)',
28
+ },
29
+ title: {
30
+ color: (0, tokens_1.tok)(t, 'color-fg-on-inverted'),
31
+ fontFamily: (0, tokens_1.tok)(t, 'typography-font-family-heading'),
32
+ fontSize: (0, tokens_1.tok)(t, 'typography-font-size-2'),
33
+ fontWeight: (0, tokens_1.tok)(t, 'typography-font-weight-strong'),
34
+ lineHeight: (0, tokens_1.tok)(t, 'typography-line-height-default'),
35
+ marginBottom: (0, tokens_1.tok)(t, 'space-1'),
36
+ },
37
+ body: {
38
+ color: (0, tokens_1.tok)(t, 'color-fg-on-inverted'),
39
+ fontFamily: (0, tokens_1.tok)(t, 'typography-font-family-base'),
40
+ fontSize: (0, tokens_1.tok)(t, 'typography-font-size-2'),
41
+ fontWeight: (0, tokens_1.tok)(t, 'typography-font-weight-regular'),
42
+ lineHeight: (0, tokens_1.tok)(t, 'typography-line-height-default'),
43
+ },
44
+ };
45
+ };
46
+ exports.getTooltipParts = getTooltipParts;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./states"), exports);
18
+ __exportStar(require("./tokens"), exports);
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isInvalid = exports.isDisabled = void 0;
4
+ // Helpers pequenos (opcionais)
5
+ const isDisabled = (s) => Boolean(s?.disabled || s?.loading);
6
+ exports.isDisabled = isDisabled;
7
+ const isInvalid = (s) => Boolean(s?.error || s?.invalid);
8
+ exports.isInvalid = isInvalid;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.motion = exports.merge = exports.tok = void 0;
4
+ const styleguide_1 = require("@react-xp/styleguide");
5
+ const tok = (t, key) => t[key];
6
+ exports.tok = tok;
7
+ const merge = (...styles) => Object.assign({}, ...styles.filter(Boolean));
8
+ exports.merge = merge;
9
+ const motion = (t, preset) => {
10
+ const p = styleguide_1.styleguideMotionPreset[preset];
11
+ const durationToken = p.duration;
12
+ const easingToken = p.easing;
13
+ return {
14
+ durationMs: (0, exports.tok)(t, durationToken),
15
+ easing: (0, exports.tok)(t, easingToken),
16
+ };
17
+ };
18
+ exports.motion = motion;
@@ -0,0 +1,169 @@
1
+ // styles/a11y.checks.ts
2
+ // Checker WCAG AA para pares de tokens (bg/fg)
3
+ // - Útil para CI: garante contraste mínimo no tema (multi-tenant).
4
+ // - Sem deps externas.
5
+ const clamp01 = (n) => Math.min(1, Math.max(0, n));
6
+ const parseHex = (hex) => {
7
+ // #RGB, #RRGGBB
8
+ const h = hex.trim();
9
+ if (!h.startsWith('#'))
10
+ return null;
11
+ const raw = h.slice(1);
12
+ if (raw.length === 3 && raw[0] && raw[1] && raw[2]) {
13
+ const r = Number.parseInt(raw[0] + raw[0], 16);
14
+ const g = Number.parseInt(raw[1] + raw[1], 16);
15
+ const b = Number.parseInt(raw[2] + raw[2], 16);
16
+ return { r, g, b, a: 1 };
17
+ }
18
+ if (raw.length === 6) {
19
+ const r = Number.parseInt(raw.slice(0, 2), 16);
20
+ const g = Number.parseInt(raw.slice(2, 4), 16);
21
+ const b = Number.parseInt(raw.slice(4, 6), 16);
22
+ return { r, g, b, a: 1 };
23
+ }
24
+ return null;
25
+ };
26
+ const parseRgbLike = (input) => {
27
+ // rgb(0,0,0) / rgba(0,0,0,0.5)
28
+ const m = input.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([0-9.]+))?\s*\)/i);
29
+ if (!m)
30
+ return null;
31
+ const r = Number(m[1]);
32
+ const g = Number(m[2]);
33
+ const b = Number(m[3]);
34
+ const a = m[4] ? Number(m[4]) : 1;
35
+ return { r, g, b, a: clamp01(a) };
36
+ };
37
+ const parseColor = (input) => {
38
+ if (typeof input !== 'string')
39
+ return null;
40
+ // Nota: se usares tokens tipo "var(--x)" isto não consegue resolver.
41
+ // Ideal: no tema final, tokens sejam cores concretas (#hex / rgb(a)).
42
+ return parseHex(input) ?? parseRgbLike(input);
43
+ };
44
+ const srgbToLinear = (c) => {
45
+ const v = c / 255;
46
+ return v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
47
+ };
48
+ const relativeLuminance = (rgb) => {
49
+ const r = srgbToLinear(rgb.r);
50
+ const g = srgbToLinear(rgb.g);
51
+ const b = srgbToLinear(rgb.b);
52
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
53
+ };
54
+ const contrastRatio = (a, b) => {
55
+ // WCAG formula (ignora alpha — assume cores finais já resolvidas)
56
+ const L1 = relativeLuminance(a);
57
+ const L2 = relativeLuminance(b);
58
+ const light = Math.max(L1, L2);
59
+ const dark = Math.min(L1, L2);
60
+ return (light + 0.05) / (dark + 0.05);
61
+ };
62
+ const threshold = (level, size) => {
63
+ if (level === 'AAA')
64
+ return size === 'large' ? 4.5 : 7;
65
+ return size === 'large' ? 3 : 4.5;
66
+ };
67
+ export const defaultContrastRules = () => [
68
+ // Base surfaces
69
+ {
70
+ name: 'Default BG / Default FG',
71
+ bg: 'color-bg-default',
72
+ fg: 'color-fg-default',
73
+ },
74
+ { name: 'Surface / Default FG', bg: 'color-surface', fg: 'color-fg-default' },
75
+ {
76
+ name: 'Surface-alt / Default FG',
77
+ bg: 'color-surface-alt',
78
+ fg: 'color-fg-default',
79
+ },
80
+ {
81
+ name: 'Default BG / Muted FG',
82
+ bg: 'color-bg-default',
83
+ fg: 'color-fg-muted',
84
+ },
85
+ // Actions
86
+ {
87
+ name: 'Primary Action',
88
+ bg: 'color-action-primary',
89
+ fg: 'color-action-primary-fg',
90
+ },
91
+ {
92
+ name: 'Secondary Action',
93
+ bg: 'color-action-secondary',
94
+ fg: 'color-action-secondary-fg',
95
+ },
96
+ { name: 'Accent', bg: 'color-accent', fg: 'color-accent-fg' },
97
+ // Status
98
+ {
99
+ name: 'Success',
100
+ bg: 'color-status-success',
101
+ fg: 'color-status-success-fg',
102
+ },
103
+ {
104
+ name: 'Warning',
105
+ bg: 'color-status-warning',
106
+ fg: 'color-status-warning-fg',
107
+ },
108
+ { name: 'Error', bg: 'color-status-error', fg: 'color-status-error-fg' },
109
+ { name: 'Info', bg: 'color-status-info', fg: 'color-status-info-fg' },
110
+ // Inputs (texto/placeholder em fundo input)
111
+ {
112
+ name: 'Input BG / Default FG',
113
+ bg: 'color-input-bg',
114
+ fg: 'color-fg-default',
115
+ },
116
+ {
117
+ name: 'Input BG / Placeholder',
118
+ bg: 'color-input-bg',
119
+ fg: 'color-input-placeholder',
120
+ textSize: 'large',
121
+ }, // placeholder pode ser menos exigente; ajusta se quiseres
122
+ // Inverted (tooltip/overlays)
123
+ {
124
+ name: 'Inverted FG on Default FG',
125
+ bg: 'color-fg-default',
126
+ fg: 'color-fg-on-inverted',
127
+ },
128
+ // Disabled
129
+ {
130
+ name: 'Disabled BG / Disabled FG',
131
+ bg: 'color-state-disabled-bg',
132
+ fg: 'color-state-disabled-fg',
133
+ textSize: 'large',
134
+ },
135
+ ];
136
+ export const validateThemeContrast = (tokens, rules = defaultContrastRules()) => {
137
+ return rules.map((r) => {
138
+ const bgRaw = tokens[r.bg];
139
+ const fgRaw = tokens[r.fg];
140
+ const bg = parseColor(bgRaw);
141
+ const fg = parseColor(fgRaw);
142
+ // Se não conseguimos parse, falha (porque não dá para validar)
143
+ if (!bg || !fg) {
144
+ return {
145
+ rule: r,
146
+ contrastRatio: 0,
147
+ pass: false,
148
+ };
149
+ }
150
+ const ratio = contrastRatio(bg, fg);
151
+ const lvl = r.level ?? 'AA';
152
+ const size = r.textSize ?? 'normal';
153
+ const pass = ratio >= threshold(lvl, size);
154
+ return { rule: r, contrastRatio: ratio, pass };
155
+ });
156
+ };
157
+ export const getContrastFailures = (results) => results.filter((r) => !r.pass);
158
+ export const assertThemeContrast = (tokens, rules) => {
159
+ const results = validateThemeContrast(tokens, rules);
160
+ const fails = getContrastFailures(results);
161
+ if (fails.length === 0)
162
+ return;
163
+ const lines = fails.map((f) => {
164
+ const lvl = f.rule.level ?? 'AA';
165
+ const size = f.rule.textSize ?? 'normal';
166
+ return `- ${f.rule.name}: ${String(f.rule.bg)} / ${String(f.rule.fg)} => ${f.contrastRatio.toFixed(2)} (min ${threshold(lvl, size)})`;
167
+ });
168
+ throw new Error(`Theme contrast validation failed:\n${lines.join('\n')}`);
169
+ };
@@ -0,0 +1,2 @@
1
+ export * from './a11y';
2
+ export * from './styles';
@@ -0,0 +1,158 @@
1
+ // styles/style-bridge.ts
2
+ // Binding helpers (Web + React Native):
3
+ // - normalização px<->number
4
+ // - filtrar props web-only/native-only
5
+ // - mapear shadow tokens (CSS box-shadow) para RN shadow props
6
+ // - focus ring cross-platform (wrapper approach)
7
+ // Nota: aqui não dependemos das tuas primitives — isto é “infra” para o teu runtime/binding.
8
+ import { tok, } from '../types';
9
+ /**
10
+ * Tokens utilitários são string com px (ex.: "1px", "44px")
11
+ * No RN queres number (dp), no Web queres string (px) — define UMA regra no teu binder.
12
+ */
13
+ export const toNumber = (value) => {
14
+ if (typeof value === 'number')
15
+ return value;
16
+ const n = Number.parseFloat(value);
17
+ if (Number.isNaN(n)) {
18
+ throw new Error(`toNumber: cannot parse "${value}"`);
19
+ }
20
+ return n;
21
+ };
22
+ export const toPx = (value) => {
23
+ if (typeof value === 'string')
24
+ return value;
25
+ return `${value}px`;
26
+ };
27
+ export const normalizeValueForPlatform = (platform, value) => {
28
+ // Recomendação: RN -> number; Web -> string (se vier "px")
29
+ if (platform === 'native')
30
+ return toNumber(value);
31
+ return typeof value === 'string' ? value : toPx(value);
32
+ };
33
+ export const getBorderWidthDefault = (t, platform) => normalizeValueForPlatform(platform, tok(t, 'border-width-default'));
34
+ export const getBorderWidthFocus = (t, platform) => normalizeValueForPlatform(platform, tok(t, 'border-width-focus'));
35
+ export const getTapTargetMin = (t, platform) => normalizeValueForPlatform(platform, tok(t, 'tap-target-min'));
36
+ export const getFocusRingGap = (t, platform) => normalizeValueForPlatform(platform, tok(t, 'focus-ring-gap'));
37
+ /**
38
+ * Filtragem de props por plataforma:
39
+ * - Evita drift: pessoas a usarem `outline*` em RN sem perceberem que não faz nada.
40
+ */
41
+ const WEB_ONLY_KEYS = new Set([
42
+ 'outlineStyle',
43
+ 'outlineWidth',
44
+ 'outlineColor',
45
+ 'outlineOffset',
46
+ 'inset',
47
+ 'boxShadow',
48
+ 'cursor',
49
+ ]);
50
+ const NATIVE_ONLY_KEYS = new Set([
51
+ 'elevation',
52
+ 'shadowColor',
53
+ 'shadowOpacity',
54
+ 'shadowRadius',
55
+ 'shadowOffset',
56
+ ]);
57
+ export const pickSxForPlatform = (platform, sx) => {
58
+ const out = {};
59
+ for (const [k, v] of Object.entries(sx)) {
60
+ if (v === undefined)
61
+ continue;
62
+ if (platform === 'web') {
63
+ if (NATIVE_ONLY_KEYS.has(k))
64
+ continue;
65
+ out[k] = v;
66
+ }
67
+ else {
68
+ if (WEB_ONLY_KEYS.has(k))
69
+ continue;
70
+ out[k] = v;
71
+ }
72
+ }
73
+ return out;
74
+ };
75
+ const parseRgba = (input) => {
76
+ // rgba(15, 23, 42, 0.18)
77
+ const m = input.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([0-9.]+))?\s*\)/i);
78
+ if (!m)
79
+ return null;
80
+ const r = Number(m[1]);
81
+ const g = Number(m[2]);
82
+ const b = Number(m[3]);
83
+ const a = m[4] ? Number(m[4]) : 1;
84
+ return { r, g, b, a };
85
+ };
86
+ const rgbaToHex = ({ r, g, b }) => {
87
+ const to2 = (n) => Math.max(0, Math.min(255, n)).toString(16).padStart(2, '0');
88
+ return `#${to2(r)}${to2(g)}${to2(b)}`;
89
+ };
90
+ const parseFirstBoxShadow = (shadow) => {
91
+ // Ex.: "0 10px 15px rgba(15, 23, 42, 0.18), 0 4px 6px rgba(...)"
92
+ // Vamos pegar só na primeira parte antes da vírgula de separação de shadows (não a do rgba).
93
+ const parts = shadow.split('),').slice(0, 1).join('),');
94
+ const rgba = parseRgba(parts);
95
+ if (!rgba)
96
+ return null;
97
+ // Tenta capturar "x y blur"
98
+ // "0 10px 15px rgba(...)"
99
+ const mm = parts.match(/(-?\d+(?:\.\d+)?)px?\s+(-?\d+(?:\.\d+)?)px?\s+(\d+(?:\.\d+)?)px?/);
100
+ if (!mm)
101
+ return null;
102
+ const offsetX = Number(mm[1]);
103
+ const offsetY = Number(mm[2]);
104
+ const blur = Number(mm[3]);
105
+ return { rgba, offsetX, offsetY, blur };
106
+ };
107
+ export const resolveShadow = (t, platform, shadowToken) => {
108
+ const shadowStr = tok(t, shadowToken);
109
+ if (platform === 'web') {
110
+ return { boxShadow: shadowStr };
111
+ }
112
+ const parsed = parseFirstBoxShadow(shadowStr);
113
+ if (!parsed) {
114
+ // fallback seguro (sem shadow)
115
+ return {};
116
+ }
117
+ const { rgba, offsetX, offsetY, blur } = parsed;
118
+ // Heurística RN:
119
+ // - shadowRadius: blur/2 (aproximação)
120
+ // - elevation: blur/2 (aproximação)
121
+ // - shadowOffset: offset
122
+ return {
123
+ shadowColor: rgbaToHex(rgba),
124
+ shadowOpacity: rgba.a,
125
+ shadowRadius: Math.max(0, blur / 2),
126
+ shadowOffset: { width: offsetX, height: offsetY },
127
+ elevation: Math.max(1, Math.round(blur / 2)),
128
+ };
129
+ };
130
+ export const getFocusRingSx = (t, platform, opts) => {
131
+ const borderRadius = opts?.borderRadius;
132
+ if (platform === 'web') {
133
+ return {
134
+ outlineStyle: 'solid',
135
+ outlineWidth: tok(t, 'border-width-focus'),
136
+ outlineColor: tok(t, 'color-focus-ring'),
137
+ outlineOffset: tok(t, 'focus-ring-gap'),
138
+ borderRadius,
139
+ };
140
+ }
141
+ // RN: recomendação prática
142
+ return {
143
+ borderWidth: tok(t, 'border-width-focus'),
144
+ borderColor: tok(t, 'color-focus-ring'),
145
+ borderRadius,
146
+ // um “glow” leve com base no shadow token (se quiseres)
147
+ ...resolveShadow(t, 'native', 'shadow-level-2'),
148
+ };
149
+ };
150
+ /**
151
+ * Sugestão de aplicação:
152
+ * - envolver o componente num wrapper quando focusVisible/focused
153
+ * - wrapper recebe getFocusRingSx(...), e dentro um padding = focus-ring-gap (se quiseres)
154
+ */
155
+ export const getFocusWrapperSx = (t, platform, opts) => ({
156
+ padding: getFocusRingGap(t, platform),
157
+ borderRadius: opts?.borderRadius,
158
+ });
@@ -0,0 +1,3 @@
1
+ export * from './helpers';
2
+ export * from './styles';
3
+ export * from './types';
@@ -0,0 +1,59 @@
1
+ // styles/avatars.ts
2
+ import { tok } from '../types/tokens';
3
+ const sizeMap = (t) => ({
4
+ xs: {
5
+ size: tok(t, 'space-5'),
6
+ font: tok(t, 'typography-font-size-1'),
7
+ }, // 24 / 12
8
+ sm: {
9
+ size: tok(t, 'space-6'),
10
+ font: tok(t, 'typography-font-size-2'),
11
+ }, // 32 / 14
12
+ md: {
13
+ size: tok(t, 'space-7'),
14
+ font: tok(t, 'typography-font-size-3'),
15
+ }, // 40 / 16
16
+ lg: {
17
+ size: tok(t, 'space-8'),
18
+ font: tok(t, 'typography-font-size-4'),
19
+ }, // sem token exato (P3 ok)
20
+ xl: {
21
+ size: tok(t, 'space-9'),
22
+ font: tok(t, 'typography-font-size-5'),
23
+ }, // sem token exato (P3 ok)
24
+ });
25
+ export const getAvatarParts = (t, size = 'md') => {
26
+ const s = sizeMap(t)[size];
27
+ return {
28
+ container: {
29
+ width: s.size,
30
+ height: s.size,
31
+ borderRadius: tok(t, 'radius-full'),
32
+ backgroundColor: tok(t, 'color-surface-alt'),
33
+ borderWidth: tok(t, 'border-width-default'),
34
+ borderColor: tok(t, 'color-border-subtle'),
35
+ alignItems: 'center',
36
+ justifyContent: 'center',
37
+ overflow: 'hidden',
38
+ },
39
+ image: { width: '100%', height: '100%' },
40
+ initials: {
41
+ color: tok(t, 'color-fg-default'),
42
+ fontFamily: tok(t, 'typography-font-family-base'),
43
+ fontSize: s.font,
44
+ fontWeight: tok(t, 'typography-font-weight-strong'),
45
+ lineHeight: tok(t, 'typography-line-height-tight'),
46
+ },
47
+ statusDot: {
48
+ position: 'absolute',
49
+ right: tok(t, 'space-1'),
50
+ bottom: tok(t, 'space-1'),
51
+ width: tok(t, 'space-2'),
52
+ height: tok(t, 'space-2'),
53
+ borderRadius: tok(t, 'radius-full'),
54
+ backgroundColor: tok(t, 'color-status-success'),
55
+ borderWidth: tok(t, 'border-width-default'),
56
+ borderColor: tok(t, 'color-surface'),
57
+ },
58
+ };
59
+ };
@@ -0,0 +1,55 @@
1
+ // styles/badges.ts
2
+ import { tok } from '../types/tokens';
3
+ export const getBadgeParts = (t, variant = 'neutral') => {
4
+ const map = {
5
+ neutral: {
6
+ bg: tok(t, 'color-surface-alt'),
7
+ fg: tok(t, 'color-fg-default'),
8
+ border: tok(t, 'color-border-subtle'),
9
+ },
10
+ brand: {
11
+ bg: tok(t, 'color-brand'),
12
+ fg: tok(t, 'color-fg-on-inverted'),
13
+ border: tok(t, 'color-brand'),
14
+ },
15
+ success: {
16
+ bg: tok(t, 'color-status-success'),
17
+ fg: tok(t, 'color-status-success-fg'),
18
+ border: tok(t, 'color-status-success'),
19
+ },
20
+ warning: {
21
+ bg: tok(t, 'color-status-warning'),
22
+ fg: tok(t, 'color-status-warning-fg'),
23
+ border: tok(t, 'color-status-warning'),
24
+ },
25
+ error: {
26
+ bg: tok(t, 'color-status-error'),
27
+ fg: tok(t, 'color-status-error-fg'),
28
+ border: tok(t, 'color-status-error'),
29
+ },
30
+ info: {
31
+ bg: tok(t, 'color-status-info'),
32
+ fg: tok(t, 'color-status-info-fg'),
33
+ border: tok(t, 'color-status-info'),
34
+ },
35
+ };
36
+ const v = map[variant];
37
+ return {
38
+ container: {
39
+ alignSelf: 'flex-start',
40
+ backgroundColor: v.bg,
41
+ borderColor: v.border,
42
+ borderWidth: tok(t, 'border-width-default'),
43
+ borderRadius: tok(t, 'radius-full'),
44
+ paddingVertical: tok(t, 'space-1'),
45
+ paddingHorizontal: tok(t, 'space-2'),
46
+ },
47
+ label: {
48
+ color: v.fg,
49
+ fontFamily: tok(t, 'typography-font-family-base'),
50
+ fontSize: tok(t, 'typography-font-size-1'),
51
+ fontWeight: tok(t, 'typography-font-weight-medium'),
52
+ lineHeight: tok(t, 'typography-line-height-default'),
53
+ },
54
+ };
55
+ };