@simplybusiness/mobius 10.0.0 → 10.1.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 (113) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cjs/components/AddressLookup/AddressLookup.js +9 -4
  3. package/dist/cjs/components/AddressLookup/AddressLookup.js.map +2 -2
  4. package/dist/cjs/components/AddressLookup/index.js +9 -4
  5. package/dist/cjs/components/AddressLookup/index.js.map +2 -2
  6. package/dist/cjs/components/Checkbox/Checkbox.js +9 -4
  7. package/dist/cjs/components/Checkbox/Checkbox.js.map +2 -2
  8. package/dist/cjs/components/Checkbox/CheckboxGroup.js +9 -4
  9. package/dist/cjs/components/Checkbox/CheckboxGroup.js.map +2 -2
  10. package/dist/cjs/components/Checkbox/index.js +9 -4
  11. package/dist/cjs/components/Checkbox/index.js.map +2 -2
  12. package/dist/cjs/components/Combobox/Combobox.js +9 -4
  13. package/dist/cjs/components/Combobox/Combobox.js.map +2 -2
  14. package/dist/cjs/components/Combobox/index.js +9 -4
  15. package/dist/cjs/components/Combobox/index.js.map +2 -2
  16. package/dist/cjs/components/DateField/DateField.js +9 -4
  17. package/dist/cjs/components/DateField/DateField.js.map +2 -2
  18. package/dist/cjs/components/DateField/index.js +9 -4
  19. package/dist/cjs/components/DateField/index.js.map +2 -2
  20. package/dist/cjs/components/ErrorMessage/ErrorMessage.js +9 -4
  21. package/dist/cjs/components/ErrorMessage/ErrorMessage.js.map +2 -2
  22. package/dist/cjs/components/ErrorMessage/index.js +9 -4
  23. package/dist/cjs/components/ErrorMessage/index.js.map +2 -2
  24. package/dist/cjs/components/ExpandableText/ExpandableText.js +9 -4
  25. package/dist/cjs/components/ExpandableText/ExpandableText.js.map +2 -2
  26. package/dist/cjs/components/ExpandableText/index.js +9 -4
  27. package/dist/cjs/components/ExpandableText/index.js.map +2 -2
  28. package/dist/cjs/components/MaskedField/MaskedField.js +9 -4
  29. package/dist/cjs/components/MaskedField/MaskedField.js.map +2 -2
  30. package/dist/cjs/components/MaskedField/index.js +9 -4
  31. package/dist/cjs/components/MaskedField/index.js.map +2 -2
  32. package/dist/cjs/components/NumberField/NumberField.js +9 -4
  33. package/dist/cjs/components/NumberField/NumberField.js.map +2 -2
  34. package/dist/cjs/components/NumberField/index.js +9 -4
  35. package/dist/cjs/components/NumberField/index.js.map +2 -2
  36. package/dist/cjs/components/PasswordField/PasswordField.js +9 -4
  37. package/dist/cjs/components/PasswordField/PasswordField.js.map +2 -2
  38. package/dist/cjs/components/PasswordField/ShowHideButton.js +9 -4
  39. package/dist/cjs/components/PasswordField/ShowHideButton.js.map +2 -2
  40. package/dist/cjs/components/PasswordField/index.js +9 -4
  41. package/dist/cjs/components/PasswordField/index.js.map +2 -2
  42. package/dist/cjs/components/Radio/Radio.js +9 -4
  43. package/dist/cjs/components/Radio/Radio.js.map +2 -2
  44. package/dist/cjs/components/Radio/RadioGroup.js +9 -4
  45. package/dist/cjs/components/Radio/RadioGroup.js.map +2 -2
  46. package/dist/cjs/components/Radio/index.js +9 -4
  47. package/dist/cjs/components/Radio/index.js.map +2 -2
  48. package/dist/cjs/components/Select/Select.js +9 -4
  49. package/dist/cjs/components/Select/Select.js.map +2 -2
  50. package/dist/cjs/components/Select/index.js +9 -4
  51. package/dist/cjs/components/Select/index.js.map +2 -2
  52. package/dist/cjs/components/TextArea/TextArea.js +9 -4
  53. package/dist/cjs/components/TextArea/TextArea.js.map +2 -2
  54. package/dist/cjs/components/TextArea/index.js +9 -4
  55. package/dist/cjs/components/TextArea/index.js.map +2 -2
  56. package/dist/cjs/components/TextField/TextField.js +9 -4
  57. package/dist/cjs/components/TextField/TextField.js.map +2 -2
  58. package/dist/cjs/components/TextField/index.js +9 -4
  59. package/dist/cjs/components/TextField/index.js.map +2 -2
  60. package/dist/cjs/components/TextOrHTML/TextOrHTML.js +8 -3
  61. package/dist/cjs/components/TextOrHTML/TextOrHTML.js.map +2 -2
  62. package/dist/cjs/components/TextOrHTML/index.js +8 -3
  63. package/dist/cjs/components/TextOrHTML/index.js.map +2 -2
  64. package/dist/cjs/components/index.js +9 -4
  65. package/dist/cjs/components/index.js.map +2 -2
  66. package/dist/cjs/index.js +9 -4
  67. package/dist/cjs/index.js.map +2 -2
  68. package/dist/cjs/meta.json +100 -100
  69. package/dist/esm/{chunk-ZU3XMAWQ.js → chunk-C4BILMFX.js} +2 -2
  70. package/dist/esm/{chunk-HHJ4Y5JQ.js → chunk-QRHDVVRK.js} +9 -4
  71. package/dist/esm/chunk-QRHDVVRK.js.map +7 -0
  72. package/dist/esm/components/AddressLookup/AddressLookup.js +2 -2
  73. package/dist/esm/components/AddressLookup/index.js +2 -2
  74. package/dist/esm/components/Checkbox/Checkbox.js +2 -2
  75. package/dist/esm/components/Checkbox/CheckboxGroup.js +2 -2
  76. package/dist/esm/components/Checkbox/index.js +2 -2
  77. package/dist/esm/components/Combobox/Combobox.js +2 -2
  78. package/dist/esm/components/Combobox/index.js +2 -2
  79. package/dist/esm/components/DateField/DateField.js +2 -2
  80. package/dist/esm/components/DateField/index.js +2 -2
  81. package/dist/esm/components/ErrorMessage/ErrorMessage.js +2 -2
  82. package/dist/esm/components/ErrorMessage/index.js +2 -2
  83. package/dist/esm/components/ExpandableText/ExpandableText.js +2 -2
  84. package/dist/esm/components/ExpandableText/index.js +2 -2
  85. package/dist/esm/components/MaskedField/MaskedField.js +2 -2
  86. package/dist/esm/components/MaskedField/index.js +2 -2
  87. package/dist/esm/components/NumberField/NumberField.js +2 -2
  88. package/dist/esm/components/NumberField/index.js +2 -2
  89. package/dist/esm/components/PasswordField/PasswordField.js +2 -2
  90. package/dist/esm/components/PasswordField/ShowHideButton.js +2 -2
  91. package/dist/esm/components/PasswordField/index.js +2 -2
  92. package/dist/esm/components/Radio/Radio.js +2 -2
  93. package/dist/esm/components/Radio/RadioGroup.js +2 -2
  94. package/dist/esm/components/Radio/index.js +2 -2
  95. package/dist/esm/components/Select/Select.js +2 -2
  96. package/dist/esm/components/Select/index.js +2 -2
  97. package/dist/esm/components/TextArea/TextArea.js +2 -2
  98. package/dist/esm/components/TextArea/index.js +2 -2
  99. package/dist/esm/components/TextField/TextField.js +2 -2
  100. package/dist/esm/components/TextField/index.js +2 -2
  101. package/dist/esm/components/TextOrHTML/TextOrHTML.js +1 -1
  102. package/dist/esm/components/TextOrHTML/index.js +1 -1
  103. package/dist/esm/components/index.js +2 -2
  104. package/dist/esm/index.js +2 -2
  105. package/dist/esm/meta.json +73 -73
  106. package/dist/tsconfig.build.tsbuildinfo +1 -1
  107. package/dist/types/components/TextOrHTML/TextOrHTML.d.ts +3 -1
  108. package/package.json +1 -1
  109. package/src/components/Button/Button.css +44 -14
  110. package/src/components/TextOrHTML/TextOrHTML.test.tsx +95 -0
  111. package/src/components/TextOrHTML/TextOrHTML.tsx +13 -3
  112. package/dist/esm/chunk-HHJ4Y5JQ.js.map +0 -7
  113. /package/dist/esm/{chunk-ZU3XMAWQ.js.map → chunk-C4BILMFX.js.map} +0 -0
@@ -10,9 +10,11 @@ export interface TextOrHTMLProps extends Omit<TextProps, "children">, RefAttribu
10
10
  htmlElementType?: HTMLElementType;
11
11
  /** If true, wraps the dangerous HTML element inside a Text component */
12
12
  textWrapper?: boolean;
13
+ /** If true, auto-detects whether text is HTML or plain text to determine wrapping and element type */
14
+ autoDetect?: boolean;
13
15
  }
14
16
  declare const TextOrHTML: {
15
- ({ ref, text, htmlClassName, htmlElementType, textWrapper, ...textProps }: TextOrHTMLProps): import("react/jsx-runtime").JSX.Element;
17
+ ({ ref, text, htmlClassName, htmlElementType, textWrapper, autoDetect, ...textProps }: TextOrHTMLProps): import("react/jsx-runtime").JSX.Element;
16
18
  displayName: string;
17
19
  };
18
20
  export { TextOrHTML };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@simplybusiness/mobius",
3
3
  "license": "UNLICENSED",
4
- "version": "10.0.0",
4
+ "version": "10.1.1",
5
5
  "description": "Core library of Mobius react components",
6
6
  "repository": {
7
7
  "type": "git",
@@ -50,11 +50,16 @@
50
50
  background: var(--button-primary-color);
51
51
  font-variation-settings: var(--button-primary-font-variation);
52
52
 
53
- &:where(:active),
54
- &:where(:hover) {
53
+ &:where(:active) {
55
54
  background: var(--button-primary-hover-color);
56
55
  }
57
56
 
57
+ @media (hover: hover) {
58
+ &:where(:hover) {
59
+ background: var(--button-primary-hover-color);
60
+ }
61
+ }
62
+
58
63
  &:where(:focus-visible) {
59
64
  box-shadow: var(--box-shadow-default);
60
65
  }
@@ -72,13 +77,20 @@
72
77
  background: transparent;
73
78
  border-color: var(--button-secondary-color);
74
79
 
75
- &:where(:active),
76
- &:where(:hover) {
80
+ &:where(:active) {
77
81
  --button-content-color: var(--color-text-inverted);
78
82
  background: var(--button-secondary-hover-color);
79
83
  border-color: var(--button-secondary-hover-color);
80
84
  }
81
85
 
86
+ @media (hover: hover) {
87
+ &:where(:hover) {
88
+ --button-content-color: var(--color-text-inverted);
89
+ background: var(--button-secondary-hover-color);
90
+ border-color: var(--button-secondary-hover-color);
91
+ }
92
+ }
93
+
82
94
  &:where(:focus-visible) {
83
95
  box-shadow: var(--box-shadow-default);
84
96
  }
@@ -99,15 +111,19 @@
99
111
  border-color: transparent;
100
112
 
101
113
  &:where(:active) {
102
- background: var(--color-secondary-hover);
103
- }
104
-
105
- &:where(:hover) {
106
114
  --button-content-color: var(--color-text-inverted);
107
115
  border-color: transparent;
108
116
  background: var(--color-secondary-hover);
109
117
  }
110
118
 
119
+ @media (hover: hover) {
120
+ &:where(:hover) {
121
+ --button-content-color: var(--color-text-inverted);
122
+ border-color: transparent;
123
+ background: var(--color-secondary-hover);
124
+ }
125
+ }
126
+
111
127
  &:where(:focus-visible) {
112
128
  box-shadow: var(--box-shadow-default);
113
129
  }
@@ -130,8 +146,10 @@
130
146
  background: var(--color-background-light);
131
147
  }
132
148
 
133
- &:where(:hover) {
134
- background: var(--color-background-light);
149
+ @media (hover: hover) {
150
+ &:where(:hover) {
151
+ background: var(--color-background-light);
152
+ }
135
153
  }
136
154
 
137
155
  &:where(:focus-visible) {
@@ -167,9 +185,11 @@
167
185
  fill: var(--button-content-color);
168
186
  }
169
187
 
170
- &:where(:hover) {
171
- background: var(--color-valid-hover);
172
- border-color: var(--color-valid-hover);
188
+ @media (hover: hover) {
189
+ &:where(:hover) {
190
+ background: var(--color-valid-hover);
191
+ border-color: var(--color-valid-hover);
192
+ }
173
193
  }
174
194
 
175
195
  &:where(:active) {
@@ -217,7 +237,6 @@
217
237
  text-decoration: underline;
218
238
  }
219
239
 
220
- &:hover,
221
240
  &:active {
222
241
  --button-content-color: var(
223
242
  --button-link-hover-color,
@@ -227,6 +246,17 @@
227
246
  cursor: pointer;
228
247
  }
229
248
 
249
+ @media (hover: hover) {
250
+ &:hover {
251
+ --button-content-color: var(
252
+ --button-link-hover-color,
253
+ var(--color-secondary-hover)
254
+ );
255
+ text-decoration: var(--button-link-hover-text-decoration, underline);
256
+ cursor: pointer;
257
+ }
258
+ }
259
+
230
260
  &:focus-visible {
231
261
  box-shadow: var(--button-link-box-shadow, var(--box-shadow-default));
232
262
  }
@@ -127,6 +127,101 @@ describe("TextOrHTML", () => {
127
127
  });
128
128
  });
129
129
 
130
+ describe("autoDetect prop", () => {
131
+ it("detects HTML content and renders as a div without Text wrapper", () => {
132
+ const { container } = render(
133
+ <TextOrHTML text="<strong>Bold text</strong>" autoDetect />,
134
+ );
135
+ expect(container.firstChild?.nodeName).toBe("DIV");
136
+ // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
137
+ const div = container.querySelector("div");
138
+ expect(div?.innerHTML).toBe("<strong>Bold text</strong>");
139
+ });
140
+
141
+ it("detects plain text and wraps in a Text component with a span", () => {
142
+ render(
143
+ <TextOrHTML text="Just plain text" autoDetect data-testid="test" />,
144
+ );
145
+ const element = screen.getByTestId("test");
146
+ expect(element).toHaveClass("mobius");
147
+ expect(element).toHaveClass(CLASS_NAME);
148
+ // eslint-disable-next-line testing-library/no-node-access
149
+ const span = element.querySelector("span");
150
+ expect(span?.innerHTML).toBe("Just plain text");
151
+ });
152
+
153
+ it("detects HTML with leading whitespace", () => {
154
+ const { container } = render(
155
+ <TextOrHTML text=" <div>Indented HTML</div>" autoDetect />,
156
+ );
157
+ expect(container.firstChild?.nodeName).toBe("DIV");
158
+ });
159
+
160
+ it("treats text not starting with a tag as plain text", () => {
161
+ render(<TextOrHTML text="Hello world" autoDetect data-testid="test" />);
162
+ expect(screen.getByTestId("test")).toHaveClass(CLASS_NAME);
163
+ });
164
+
165
+ it("does not auto-detect when autoDetect is false", () => {
166
+ const { container } = render(<TextOrHTML text="Plain text" />);
167
+ // Without autoDetect, defaults to span with no Text wrapper
168
+ expect(container.firstChild?.nodeName).toBe("SPAN");
169
+ // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
170
+ expect(container.querySelector(`.${CLASS_NAME}`)).toBeNull();
171
+ });
172
+
173
+ it("respects explicit htmlElementType even with autoDetect", () => {
174
+ const { container } = render(
175
+ <TextOrHTML
176
+ htmlElementType="span"
177
+ text="<strong>HTML</strong>"
178
+ autoDetect
179
+ />,
180
+ );
181
+ expect(container.firstChild?.nodeName).toBe("SPAN");
182
+ });
183
+
184
+ it("wraps HTML in Text when both autoDetect and textWrapper are true", () => {
185
+ render(
186
+ <TextOrHTML
187
+ text="<strong>Bold</strong>"
188
+ htmlElementType="span"
189
+ autoDetect
190
+ textWrapper
191
+ data-testid="test"
192
+ />,
193
+ );
194
+ const element = screen.getByTestId("test");
195
+ expect(element).toHaveClass(CLASS_NAME);
196
+ // eslint-disable-next-line testing-library/no-node-access
197
+ const span = element.querySelector("span");
198
+ expect(span?.innerHTML).toBe("<strong>Bold</strong>");
199
+ });
200
+
201
+ it("passes className to Text wrapper when autoDetect wraps plain text", () => {
202
+ render(
203
+ <TextOrHTML
204
+ text="Plain text"
205
+ autoDetect
206
+ className="custom-class"
207
+ data-testid="test"
208
+ />,
209
+ );
210
+ const element = screen.getByTestId("test");
211
+ expect(element).toHaveClass(CLASS_NAME);
212
+ expect(element).toHaveClass("custom-class");
213
+ });
214
+
215
+ it("does not wrap in Text when autoDetect is off and textWrapper is omitted", () => {
216
+ const { container } = render(
217
+ <TextOrHTML text="Plain text, no wrapper" />,
218
+ );
219
+ expect(container.firstChild?.nodeName).toBe("SPAN");
220
+ // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
221
+ expect(container.querySelector(`.${CLASS_NAME}`)).toBeNull();
222
+ });
223
+ });
224
+
130
225
  // See: https://github.com/facebook/react/issues/31660
131
226
  describe("React 19 dangerouslySetInnerHTML regression", () => {
132
227
  it("should not replace innerHTML on re-render when text prop hasn't changed", async () => {
@@ -5,6 +5,8 @@ import { Text } from "../Text/Text";
5
5
 
6
6
  export type HTMLElementType = "span" | "div";
7
7
 
8
+ const isHTML = (text: string) => /^\s*<[a-z]/i.test(text);
9
+
8
10
  export interface TextOrHTMLProps
9
11
  extends Omit<TextProps, "children">, RefAttributes<TextElementType> {
10
12
  /** HTML string to be rendered with dangerouslySetInnerHTML */
@@ -15,17 +17,25 @@ export interface TextOrHTMLProps
15
17
  htmlElementType?: HTMLElementType;
16
18
  /** If true, wraps the dangerous HTML element inside a Text component */
17
19
  textWrapper?: boolean;
20
+ /** If true, auto-detects whether text is HTML or plain text to determine wrapping and element type */
21
+ autoDetect?: boolean;
18
22
  }
19
23
 
20
24
  const TextOrHTML = ({
21
25
  ref,
22
26
  text,
23
27
  htmlClassName,
24
- htmlElementType = "span",
28
+ htmlElementType,
25
29
  textWrapper = false,
30
+ autoDetect = false,
26
31
  ...textProps
27
32
  }: TextOrHTMLProps) => {
28
- const DangerousComponent = htmlElementType;
33
+ const textIsHTML = autoDetect && isHTML(text);
34
+ const shouldWrapInText = autoDetect
35
+ ? textWrapper || !textIsHTML
36
+ : textWrapper;
37
+ const resolvedElementType = htmlElementType ?? (textIsHTML ? "div" : "span");
38
+ const DangerousComponent = resolvedElementType;
29
39
 
30
40
  // Memoize the dangerouslySetInnerHTML object to prevent unnecessary re-renders
31
41
  // See: https://github.com/facebook/react/issues/31660
@@ -38,7 +48,7 @@ const TextOrHTML = ({
38
48
  />
39
49
  );
40
50
 
41
- if (textWrapper) {
51
+ if (shouldWrapInText) {
42
52
  return (
43
53
  <Text ref={ref} {...textProps}>
44
54
  {dangerousElement}
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/components/TextOrHTML/TextOrHTML.tsx"],
4
- "sourcesContent": ["import type { RefAttributes } from \"react\";\nimport { useMemo } from \"react\";\nimport type { TextElementType, TextProps } from \"../Text/Text\";\nimport { Text } from \"../Text/Text\";\n\nexport type HTMLElementType = \"span\" | \"div\";\n\nexport interface TextOrHTMLProps\n extends Omit<TextProps, \"children\">, RefAttributes<TextElementType> {\n /** HTML string to be rendered with dangerouslySetInnerHTML */\n text: string;\n /** Custom class name for the dangerous HTML element */\n htmlClassName?: string;\n /** HTML element type for the dangerous HTML element */\n htmlElementType?: HTMLElementType;\n /** If true, wraps the dangerous HTML element inside a Text component */\n textWrapper?: boolean;\n}\n\nconst TextOrHTML = ({\n ref,\n text,\n htmlClassName,\n htmlElementType = \"span\",\n textWrapper = false,\n ...textProps\n}: TextOrHTMLProps) => {\n const DangerousComponent = htmlElementType;\n\n // Memoize the dangerouslySetInnerHTML object to prevent unnecessary re-renders\n // See: https://github.com/facebook/react/issues/31660\n const dangerousHTML = useMemo(() => ({ __html: text }), [text]);\n\n const dangerousElement = (\n <DangerousComponent\n className={htmlClassName}\n dangerouslySetInnerHTML={dangerousHTML}\n />\n );\n\n if (textWrapper) {\n return (\n <Text ref={ref} {...textProps}>\n {dangerousElement}\n </Text>\n );\n }\n\n return dangerousElement;\n};\n\nTextOrHTML.displayName = \"TextOrHTML\";\nexport { TextOrHTML };\n"],
5
- "mappings": ";;;;;AACA,SAAS,eAAe;AAiCpB;AAfJ,IAAM,aAAa,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA,kBAAkB;AAAA,EAClB,cAAc;AAAA,EACd,GAAG;AACL,MAAuB;AACrB,QAAM,qBAAqB;AAI3B,QAAM,gBAAgB,QAAQ,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC,IAAI,CAAC;AAE9D,QAAM,mBACJ;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,MACX,yBAAyB;AAAA;AAAA,EAC3B;AAGF,MAAI,aAAa;AACf,WACE,oBAAC,QAAK,KAAW,GAAG,WACjB,4BACH;AAAA,EAEJ;AAEA,SAAO;AACT;AAEA,WAAW,cAAc;",
6
- "names": []
7
- }