@simplybusiness/mobius 10.4.4 → 10.6.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 (250) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/components/AddressLookup/LoqateAddressLookupService.js +28 -40
  3. package/dist/cjs/components/AddressLookup/LoqateAddressLookupService.js.map +1 -1
  4. package/dist/cjs/components/AddressLookup/index.js +28 -40
  5. package/dist/cjs/components/AddressLookup/index.js.map +1 -1
  6. package/dist/cjs/components/StickyOnMobile/StickyOnMobile.js +49 -0
  7. package/dist/cjs/components/StickyOnMobile/StickyOnMobile.js.map +7 -0
  8. package/dist/cjs/components/StickyOnMobile/index.js +51 -0
  9. package/dist/cjs/components/StickyOnMobile/index.js.map +7 -0
  10. package/dist/cjs/components/index.js +147 -143
  11. package/dist/cjs/components/index.js.map +4 -4
  12. package/dist/cjs/index.js +147 -143
  13. package/dist/cjs/index.js.map +4 -4
  14. package/dist/cjs/meta.json +157 -20
  15. package/dist/esm/{chunk-XEP6X7JU.js → chunk-53QMWUHB.js} +2 -2
  16. package/dist/esm/{chunk-VHAA22YE.js → chunk-A7KFYNH6.js} +1 -3
  17. package/dist/esm/{chunk-6RDK3FM2.js → chunk-AKCNBW55.js} +4 -4
  18. package/dist/esm/{chunk-6JCU4CGA.js → chunk-AZUVQRYC.js} +4 -4
  19. package/dist/esm/{chunk-5QMKPWB4.js → chunk-C2QJDKXS.js} +2 -2
  20. package/dist/esm/chunk-CTY6LDFI.js +1 -0
  21. package/dist/esm/{chunk-KFHPI67N.js → chunk-GJBH37DH.js} +4 -4
  22. package/dist/esm/{chunk-QNOBB5HT.js → chunk-IF7FRSC2.js} +2 -2
  23. package/dist/esm/chunk-L2X5IF3K.js +1 -0
  24. package/dist/esm/chunk-L2X5IF3K.js.map +7 -0
  25. package/dist/esm/{chunk-2HUMNED2.js → chunk-M3X3ECNH.js} +4 -4
  26. package/dist/esm/chunk-NXWWQSZA.js +82 -0
  27. package/dist/esm/{chunk-VVL4B2KD.js.map → chunk-NXWWQSZA.js.map} +1 -1
  28. package/dist/esm/{chunk-VZ3IWSK6.js → chunk-Q5RB4O4S.js} +7 -7
  29. package/dist/esm/{chunk-VGFVSRWH.js → chunk-RBB3WHBJ.js} +2 -2
  30. package/dist/esm/{chunk-R67C5QTH.js → chunk-TQWUPLN6.js} +2 -2
  31. package/dist/esm/{chunk-WSQWMVA2.js → chunk-YEZ6KR3Q.js} +2 -2
  32. package/dist/esm/chunk-YWFAKGQQ.js +19 -0
  33. package/dist/esm/chunk-YWFAKGQQ.js.map +7 -0
  34. package/dist/esm/{chunk-P6YHEIFT.js → chunk-ZLAAOGRQ.js} +4 -4
  35. package/dist/esm/components/Accordion/Accordion.js +1 -1
  36. package/dist/esm/components/Accordion/AccordionLink.js +1 -1
  37. package/dist/esm/components/Accordion/AccordionList.js +1 -1
  38. package/dist/esm/components/Accordion/index.js +1 -1
  39. package/dist/esm/components/AddressLookup/AddressLookup.js +6 -6
  40. package/dist/esm/components/AddressLookup/LoqateAddressLookupError.js +1 -1
  41. package/dist/esm/components/AddressLookup/LoqateAddressLookupService.js +2 -2
  42. package/dist/esm/components/AddressLookup/__mocks__/LoqateAddressLookupService.js +1 -1
  43. package/dist/esm/components/AddressLookup/index.js +9 -9
  44. package/dist/esm/components/AddressLookup/utils.js +1 -1
  45. package/dist/esm/components/Alert/Alert.js +1 -1
  46. package/dist/esm/components/Alert/index.js +1 -1
  47. package/dist/esm/components/Box/Box.js +1 -1
  48. package/dist/esm/components/Box/index.js +1 -1
  49. package/dist/esm/components/Breadcrumbs/BreadcrumbItem.js +1 -1
  50. package/dist/esm/components/Breadcrumbs/Breadcrumbs.js +1 -1
  51. package/dist/esm/components/Breadcrumbs/index.js +2 -2
  52. package/dist/esm/components/Button/Button.js +1 -1
  53. package/dist/esm/components/Button/Loading.js +1 -1
  54. package/dist/esm/components/Button/Success.js +1 -1
  55. package/dist/esm/components/Button/index.js +1 -1
  56. package/dist/esm/components/Checkbox/Checkbox.js +1 -1
  57. package/dist/esm/components/Checkbox/CheckboxGroup.js +3 -3
  58. package/dist/esm/components/Checkbox/index.js +3 -3
  59. package/dist/esm/components/Combobox/Combobox.js +5 -5
  60. package/dist/esm/components/Combobox/Listbox.js +1 -1
  61. package/dist/esm/components/Combobox/Option.js +1 -1
  62. package/dist/esm/components/Combobox/fixtures.js +1 -1
  63. package/dist/esm/components/Combobox/index.js +5 -5
  64. package/dist/esm/components/Combobox/useComboboxHighlight.js +1 -1
  65. package/dist/esm/components/Combobox/useComboboxOptions.js +1 -1
  66. package/dist/esm/components/Combobox/utils.js +1 -1
  67. package/dist/esm/components/Container/Container.js +1 -1
  68. package/dist/esm/components/Container/index.js +1 -1
  69. package/dist/esm/components/DateField/DateField.js +5 -5
  70. package/dist/esm/components/DateField/index.js +5 -5
  71. package/dist/esm/components/DateField/validation.js +1 -1
  72. package/dist/esm/components/Divider/Divider.js +1 -1
  73. package/dist/esm/components/Divider/index.js +1 -1
  74. package/dist/esm/components/Drawer/Content.js +1 -1
  75. package/dist/esm/components/Drawer/Drawer.js +1 -1
  76. package/dist/esm/components/Drawer/DrawerContext.js +1 -1
  77. package/dist/esm/components/Drawer/Header.js +1 -1
  78. package/dist/esm/components/Drawer/index.js +4 -4
  79. package/dist/esm/components/Drawer/useDrawer.js +1 -1
  80. package/dist/esm/components/DropdownMenu/DropdownMenu.js +1 -1
  81. package/dist/esm/components/DropdownMenu/Item.js +1 -1
  82. package/dist/esm/components/DropdownMenu/index.js +1 -1
  83. package/dist/esm/components/ErrorMessage/ErrorMessage.js +1 -1
  84. package/dist/esm/components/ErrorMessage/index.js +1 -1
  85. package/dist/esm/components/ExpandableText/ExpandableText.js +1 -1
  86. package/dist/esm/components/ExpandableText/index.js +1 -1
  87. package/dist/esm/components/Fieldset/Fieldset.js +1 -1
  88. package/dist/esm/components/Fieldset/index.js +1 -1
  89. package/dist/esm/components/Flex/Flex.js +1 -1
  90. package/dist/esm/components/Flex/index.js +1 -1
  91. package/dist/esm/components/Flex/propUtils.js +1 -1
  92. package/dist/esm/components/Grid/Grid.js +1 -1
  93. package/dist/esm/components/Grid/Item.js +1 -1
  94. package/dist/esm/components/Grid/index.js +1 -1
  95. package/dist/esm/components/Icon/Icon.js +1 -1
  96. package/dist/esm/components/Icon/index.js +1 -1
  97. package/dist/esm/components/Image/Image.js +1 -1
  98. package/dist/esm/components/Image/index.js +1 -1
  99. package/dist/esm/components/Label/Label.js +1 -1
  100. package/dist/esm/components/Label/index.js +1 -1
  101. package/dist/esm/components/Link/Link.js +1 -1
  102. package/dist/esm/components/Link/index.js +1 -1
  103. package/dist/esm/components/LinkButton/LinkButton.js +1 -1
  104. package/dist/esm/components/LinkButton/index.js +1 -1
  105. package/dist/esm/components/List/List.js +1 -1
  106. package/dist/esm/components/List/ListItem.js +1 -1
  107. package/dist/esm/components/List/index.js +1 -1
  108. package/dist/esm/components/LoadingIndicator/LoadingIndicator.js +1 -1
  109. package/dist/esm/components/LoadingIndicator/index.js +1 -1
  110. package/dist/esm/components/Logo/Logo.js +1 -1
  111. package/dist/esm/components/Logo/index.js +1 -1
  112. package/dist/esm/components/MaskedField/MaskedField.js +4 -4
  113. package/dist/esm/components/MaskedField/index.js +5 -5
  114. package/dist/esm/components/Modal/Content.js +1 -1
  115. package/dist/esm/components/Modal/Header.js +1 -1
  116. package/dist/esm/components/Modal/Modal.js +1 -1
  117. package/dist/esm/components/Modal/ModalContext.js +1 -1
  118. package/dist/esm/components/Modal/index.js +1 -1
  119. package/dist/esm/components/Modal/useModal.js +1 -1
  120. package/dist/esm/components/NumberField/NumberField.js +5 -5
  121. package/dist/esm/components/NumberField/index.js +5 -5
  122. package/dist/esm/components/Option/Option.js +1 -1
  123. package/dist/esm/components/Option/index.js +1 -1
  124. package/dist/esm/components/PasswordField/PasswordField.js +5 -5
  125. package/dist/esm/components/PasswordField/ShowHideButton.js +1 -1
  126. package/dist/esm/components/PasswordField/index.js +5 -5
  127. package/dist/esm/components/Popover/Arrow.js +1 -1
  128. package/dist/esm/components/Popover/Popover.js +4 -4
  129. package/dist/esm/components/Popover/index.js +4 -4
  130. package/dist/esm/components/Popover/useAutoUpdate.js +1 -1
  131. package/dist/esm/components/Popover/useFloatingPosition.js +1 -1
  132. package/dist/esm/components/Popover/useOutsidePress.js +1 -1
  133. package/dist/esm/components/Progress/Progress.js +1 -1
  134. package/dist/esm/components/Progress/index.js +1 -1
  135. package/dist/esm/components/Radio/Radio.js +1 -1
  136. package/dist/esm/components/Radio/RadioGroup.js +4 -4
  137. package/dist/esm/components/Radio/index.js +6 -6
  138. package/dist/esm/components/SVG/SVG.js +1 -1
  139. package/dist/esm/components/SVG/index.js +1 -1
  140. package/dist/esm/components/Segment/Segment.js +1 -1
  141. package/dist/esm/components/Segment/SegmentGroup.js +1 -1
  142. package/dist/esm/components/Segment/index.js +1 -1
  143. package/dist/esm/components/Select/Select.js +4 -4
  144. package/dist/esm/components/Select/index.js +4 -4
  145. package/dist/esm/components/Slider/Slider.js +1 -1
  146. package/dist/esm/components/Slider/helpers.js +1 -1
  147. package/dist/esm/components/Slider/index.js +1 -1
  148. package/dist/esm/components/Stack/Stack.js +1 -1
  149. package/dist/esm/components/Stack/index.js +1 -1
  150. package/dist/esm/components/StickyOnMobile/StickyOnMobile.js +8 -0
  151. package/dist/esm/components/StickyOnMobile/StickyOnMobile.js.map +7 -0
  152. package/dist/esm/components/StickyOnMobile/index.js +9 -0
  153. package/dist/esm/components/StickyOnMobile/index.js.map +7 -0
  154. package/dist/esm/components/Switch/Switch.js +1 -1
  155. package/dist/esm/components/Switch/index.js +1 -1
  156. package/dist/esm/components/Table/Body.js +1 -1
  157. package/dist/esm/components/Table/Cell.js +1 -1
  158. package/dist/esm/components/Table/Foot.js +1 -1
  159. package/dist/esm/components/Table/Head.js +1 -1
  160. package/dist/esm/components/Table/HeaderCell.js +1 -1
  161. package/dist/esm/components/Table/Row.js +1 -1
  162. package/dist/esm/components/Table/Table.js +1 -1
  163. package/dist/esm/components/Table/index.js +1 -1
  164. package/dist/esm/components/Text/Text.js +1 -1
  165. package/dist/esm/components/Text/index.js +1 -1
  166. package/dist/esm/components/TextArea/TextArea.js +4 -4
  167. package/dist/esm/components/TextArea/index.js +4 -4
  168. package/dist/esm/components/TextAreaInput/TextAreaInput.js +1 -1
  169. package/dist/esm/components/TextAreaInput/index.js +1 -1
  170. package/dist/esm/components/TextField/TextField.js +4 -4
  171. package/dist/esm/components/TextField/adornmentWithClassName.js +1 -1
  172. package/dist/esm/components/TextField/index.js +4 -4
  173. package/dist/esm/components/TextOrHTML/TextOrHTML.js +1 -1
  174. package/dist/esm/components/TextOrHTML/index.js +1 -1
  175. package/dist/esm/components/Title/Title.js +1 -1
  176. package/dist/esm/components/Title/index.js +1 -1
  177. package/dist/esm/components/Toast/Toast.js +1 -1
  178. package/dist/esm/components/Toast/ToastOptionsDoc.js +1 -1
  179. package/dist/esm/components/Toast/Toaster.js +1 -1
  180. package/dist/esm/components/Toast/index.js +1 -1
  181. package/dist/esm/components/Toast/state.js +1 -1
  182. package/dist/esm/components/Trust/Trust.js +1 -1
  183. package/dist/esm/components/Trust/TrustpilotProvider.js +1 -1
  184. package/dist/esm/components/Trust/constants.js +1 -1
  185. package/dist/esm/components/Trust/index.js +1 -1
  186. package/dist/esm/components/VisuallyHidden/VisuallyHidden.js +1 -1
  187. package/dist/esm/components/VisuallyHidden/index.js +1 -1
  188. package/dist/esm/components/index.js +65 -60
  189. package/dist/esm/hooks/index.js +1 -1
  190. package/dist/esm/hooks/useBreakpoint/index.js +1 -1
  191. package/dist/esm/hooks/useBreakpoint/useBreakpoint.js +1 -1
  192. package/dist/esm/hooks/useButton/index.js +1 -1
  193. package/dist/esm/hooks/useButton/useButton.js +1 -1
  194. package/dist/esm/hooks/useDialog/index.js +1 -1
  195. package/dist/esm/hooks/useDialog/useDialog.js +1 -1
  196. package/dist/esm/hooks/useDialogPolyfill/index.js +1 -1
  197. package/dist/esm/hooks/useDialogPolyfill/useDialogPolyfill.js +1 -1
  198. package/dist/esm/hooks/useLabel/index.js +1 -1
  199. package/dist/esm/hooks/useLabel/useLabel.js +1 -1
  200. package/dist/esm/hooks/useTextField/index.js +1 -1
  201. package/dist/esm/hooks/useTextField/useTextField.js +1 -1
  202. package/dist/esm/hooks/useValidationClasses/index.js +1 -1
  203. package/dist/esm/hooks/useValidationClasses/useValidationClasses.js +1 -1
  204. package/dist/esm/index.js +65 -60
  205. package/dist/esm/meta.json +3305 -3158
  206. package/dist/esm/utils/StoryContainer.js +1 -1
  207. package/dist/esm/utils/changeCSS.js +1 -1
  208. package/dist/esm/utils/colours.js +1 -1
  209. package/dist/esm/utils/delay.js +1 -1
  210. package/dist/esm/utils/excludeControls.js +1 -1
  211. package/dist/esm/utils/filterUndefinedProps.js +1 -1
  212. package/dist/esm/utils/filterUnsetValues.js +1 -1
  213. package/dist/esm/utils/getSpacingValue.js +1 -1
  214. package/dist/esm/utils/htmlDialogPolyfill.js +1 -1
  215. package/dist/esm/utils/index.js +1 -1
  216. package/dist/esm/utils/mergeRefs.js +1 -1
  217. package/dist/esm/utils/mockMatchMedia.js +1 -1
  218. package/dist/esm/utils/polyfill-tests.js +1 -1
  219. package/dist/esm/utils/sizeClasses.js +1 -1
  220. package/dist/esm/utils/sizeOptions.js +1 -1
  221. package/dist/esm/utils/spaceDelimitedList.js +1 -1
  222. package/dist/tsconfig.build.tsbuildinfo +1 -1
  223. package/dist/types/components/StickyOnMobile/StickyOnMobile.d.ts +15 -0
  224. package/dist/types/components/StickyOnMobile/index.d.ts +1 -0
  225. package/dist/types/components/index.d.ts +1 -0
  226. package/package.json +13 -13
  227. package/src/components/Button/Button.css +10 -0
  228. package/src/components/StickyOnMobile/StickyOnMobile.css +19 -0
  229. package/src/components/StickyOnMobile/StickyOnMobile.mdx +52 -0
  230. package/src/components/StickyOnMobile/StickyOnMobile.stories.tsx +99 -0
  231. package/src/components/StickyOnMobile/StickyOnMobile.test.tsx +100 -0
  232. package/src/components/StickyOnMobile/StickyOnMobile.tsx +37 -0
  233. package/src/components/StickyOnMobile/index.tsx +1 -0
  234. package/src/components/index.tsx +1 -0
  235. package/dist/esm/chunk-NOQ27VLY.js +0 -1
  236. package/dist/esm/chunk-VVL4B2KD.js +0 -92
  237. /package/dist/esm/{chunk-XEP6X7JU.js.map → chunk-53QMWUHB.js.map} +0 -0
  238. /package/dist/esm/{chunk-NOQ27VLY.js.map → chunk-A7KFYNH6.js.map} +0 -0
  239. /package/dist/esm/{chunk-6RDK3FM2.js.map → chunk-AKCNBW55.js.map} +0 -0
  240. /package/dist/esm/{chunk-6JCU4CGA.js.map → chunk-AZUVQRYC.js.map} +0 -0
  241. /package/dist/esm/{chunk-5QMKPWB4.js.map → chunk-C2QJDKXS.js.map} +0 -0
  242. /package/dist/esm/{chunk-VHAA22YE.js.map → chunk-CTY6LDFI.js.map} +0 -0
  243. /package/dist/esm/{chunk-KFHPI67N.js.map → chunk-GJBH37DH.js.map} +0 -0
  244. /package/dist/esm/{chunk-QNOBB5HT.js.map → chunk-IF7FRSC2.js.map} +0 -0
  245. /package/dist/esm/{chunk-2HUMNED2.js.map → chunk-M3X3ECNH.js.map} +0 -0
  246. /package/dist/esm/{chunk-VZ3IWSK6.js.map → chunk-Q5RB4O4S.js.map} +0 -0
  247. /package/dist/esm/{chunk-VGFVSRWH.js.map → chunk-RBB3WHBJ.js.map} +0 -0
  248. /package/dist/esm/{chunk-R67C5QTH.js.map → chunk-TQWUPLN6.js.map} +0 -0
  249. /package/dist/esm/{chunk-WSQWMVA2.js.map → chunk-YEZ6KR3Q.js.map} +0 -0
  250. /package/dist/esm/{chunk-P6YHEIFT.js.map → chunk-ZLAAOGRQ.js.map} +0 -0
@@ -0,0 +1,15 @@
1
+ import type { RefAttributes, ReactNode, ElementType, CSSProperties, HTMLAttributes } from "react";
2
+ import "./StickyOnMobile.css";
3
+ export type StickyOnMobileElementType = HTMLElement;
4
+ export interface StickyOnMobileProps extends HTMLAttributes<StickyOnMobileElementType>, RefAttributes<StickyOnMobileElementType> {
5
+ children?: ReactNode;
6
+ /** HTML element to render. Defaults to `div`. */
7
+ elementType?: ElementType;
8
+ className?: string;
9
+ style?: CSSProperties;
10
+ }
11
+ declare function StickyOnMobile({ ref, ...props }: StickyOnMobileProps): import("react/jsx-runtime").JSX.Element;
12
+ declare namespace StickyOnMobile {
13
+ var displayName: string;
14
+ }
15
+ export { StickyOnMobile };
@@ -0,0 +1 @@
1
+ export * from "./StickyOnMobile";
@@ -34,6 +34,7 @@ export * from "./Segment";
34
34
  export * from "./Select";
35
35
  export * from "./Slider";
36
36
  export * from "./Stack";
37
+ export * from "./StickyOnMobile";
37
38
  export * from "./SVG";
38
39
  export * from "./Switch";
39
40
  export * from "./Table";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@simplybusiness/mobius",
3
3
  "license": "UNLICENSED",
4
- "version": "10.4.4",
4
+ "version": "10.6.0",
5
5
  "description": "Core library of Mobius react components",
6
6
  "repository": {
7
7
  "type": "git",
@@ -63,34 +63,34 @@
63
63
  "dist/**/*.css"
64
64
  ],
65
65
  "devDependencies": {
66
- "@eslint/compat": "^2.0.2",
67
- "@eslint/eslintrc": "^3.3.4",
68
- "@eslint/js": "^9.39.3",
69
- "@react-types/progress": "^3.5.18",
66
+ "@eslint/compat": "^2.0.5",
67
+ "@eslint/eslintrc": "^3.3.5",
68
+ "@eslint/js": "^9.39.4",
69
+ "@react-types/progress": "^3.6.0",
70
70
  "@simplybusiness/build-scripts": "^3.0.0",
71
- "@simplybusiness/eslint-config": "^2.0.6",
71
+ "@simplybusiness/eslint-config": "^2.0.7",
72
72
  "@simplybusiness/eslint-plugin": "^1.3.2",
73
73
  "@testing-library/dom": "^10.4.1",
74
74
  "@testing-library/jest-dom": "6.9.1",
75
75
  "@testing-library/react": "^16.3.2",
76
76
  "@testing-library/user-event": "^14.6.1",
77
77
  "@total-typescript/shoehorn": "^0.1.2",
78
- "@types/node": "^25.3.3",
78
+ "@types/node": "^25.6.0",
79
79
  "@types/react": "^19.2.14",
80
80
  "@types/react-dom": "^19.2.3",
81
81
  "@typescript-eslint/eslint-plugin": "^8.59.0",
82
82
  "@typescript-eslint/parser": "^8.59.0",
83
83
  "@typescript/native-preview": "7.0.0-dev.20260421.2",
84
- "@vitest/coverage-v8": "^4.0.18",
84
+ "@vitest/coverage-v8": "^4.1.4",
85
85
  "csstype": "^3.2.3",
86
- "eslint": "^9.39.3",
86
+ "eslint": "^9.39.4",
87
87
  "eslint-plugin-ssr-friendly": "^1.3.0",
88
- "prettier": "^3.8.1",
89
- "react": "^19.2.4",
90
- "react-dom": "^19.2.4",
88
+ "prettier": "^3.8.3",
89
+ "react": "^19.2.5",
90
+ "react-dom": "^19.2.5",
91
91
  "tslib": "^2.8.1",
92
92
  "typescript": "^6.0.3",
93
- "vitest": "^4.0.18"
93
+ "vitest": "^4.1.4"
94
94
  },
95
95
  "peerDependencies": {
96
96
  "react": "^19.2.0",
@@ -48,14 +48,23 @@
48
48
  &:where(.--variant-primary) {
49
49
  --button-content-color: var(--button-primary-content-color);
50
50
  background: var(--button-primary-color);
51
+ border-color: var(--button-primary-border-color);
51
52
  font-variation-settings: var(--button-primary-font-variation);
52
53
 
53
54
  &:where(:active) {
55
+ --button-content-color: var(
56
+ --button-primary-hover-content-color,
57
+ var(--button-primary-content-color)
58
+ );
54
59
  background: var(--button-primary-hover-color);
55
60
  }
56
61
 
57
62
  @media (hover: hover) {
58
63
  &:where(:hover) {
64
+ --button-content-color: var(
65
+ --button-primary-hover-content-color,
66
+ var(--button-primary-content-color)
67
+ );
59
68
  background: var(--button-primary-hover-color);
60
69
  }
61
70
  }
@@ -67,6 +76,7 @@
67
76
  &:where(.--is-disabled) {
68
77
  --button-content-color: var(--color-text-light);
69
78
  background: var(--color-background-medium);
79
+ border-color: transparent;
70
80
  cursor: not-allowed;
71
81
  }
72
82
  }
@@ -0,0 +1,19 @@
1
+ @layer atoms {
2
+ @media (max-width: 768px) {
3
+ .mobius-sticky-on-mobile {
4
+ position: fixed;
5
+ bottom: 0;
6
+ left: 0;
7
+ right: 0;
8
+ box-sizing: border-box;
9
+ padding: var(--size-sm);
10
+ padding-bottom: max(var(--size-sm), env(safe-area-inset-bottom));
11
+ background-color: var(--color-background);
12
+ /* Second shadow paints a 1px background-coloured band below the bar so sub-pixel rounding can't reveal a hairline gap. */
13
+ box-shadow: var(--shadow-card),
14
+ 0 1px 0 var(--color-background);
15
+ /* Sit above Zendesk's launcher (z-index: 999999). */
16
+ z-index: 999999;
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,52 @@
1
+ import { Meta, ArgTypes, Canvas } from "@storybook/addon-docs/blocks";
2
+ import * as StickyOnMobileStories from "./StickyOnMobile.stories";
3
+ import { StickyOnMobile } from "./StickyOnMobile";
4
+
5
+ <Meta of={StickyOnMobileStories} />
6
+
7
+ # StickyOnMobile
8
+
9
+ `StickyOnMobile` is a thin layout wrapper that pins a single child to the bottom of the viewport on mobile (`max-width: 768px`) and renders in document flow on desktop. Use it for primary CTAs such as the Combined Payments checkout button.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ yarn add @simplybusiness/mobius
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```js
20
+ import { StickyOnMobile } from "@simplybusiness/mobius";
21
+ ```
22
+
23
+ ### Default
24
+
25
+ <Canvas of={StickyOnMobileStories.Default} />
26
+
27
+ ### As a `footer` element
28
+
29
+ <Canvas of={StickyOnMobileStories.AsFooterElement} />
30
+
31
+ ## Behaviour
32
+
33
+ - **≥ 769px** — renders as a plain block element with no margin or padding. The wrapper has no material impact on layout.
34
+ - **≤ 768px** — clamped to the bottom of the viewport with `position: fixed; bottom: 0; left: 0; right: 0;`. White background (`var(--color-background)`) and a soft upward shadow visually layer the bar above the scrolling content. Padding of `var(--size-sm)` is applied, with `env(safe-area-inset-bottom)` honoured for iOS home-indicator safe area.
35
+
36
+ ## Composition
37
+
38
+ `StickyOnMobile` accepts a single child. For complex content, compose it into its own component first.
39
+
40
+ The breakpoint is fixed at 768px and is **not** dynamic. To override it, pass a `className` and rely on consumer-side CSS — `StickyOnMobile`'s rules sit inside `@layer atoms`, so any unlayered consumer rule wins.
41
+
42
+ ## Props
43
+
44
+ <ArgTypes of={StickyOnMobile} />
45
+
46
+ ## Component HTML Structure and Class names
47
+
48
+ The following HTML is rendered for a StickyOnMobile:
49
+
50
+ ```html
51
+ <div class="mobius mobius-sticky-on-mobile">{children}</div>
52
+ ```
@@ -0,0 +1,99 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import type { StickyOnMobileProps } from "./StickyOnMobile";
3
+ import { StickyOnMobile } from "./StickyOnMobile";
4
+ import { excludeControls } from "../../utils";
5
+ import { Button } from "../Button";
6
+ import { Stack } from "../Stack";
7
+ import { Text } from "../Text";
8
+ import { TextField } from "../TextField";
9
+
10
+ type StoryType = StoryObj<typeof StickyOnMobile>;
11
+
12
+ const meta: Meta<typeof StickyOnMobile> = {
13
+ title: "Layout/StickyOnMobile",
14
+ component: StickyOnMobile,
15
+ argTypes: {
16
+ ...excludeControls("className", "style", "children"),
17
+ elementType: {
18
+ control: { type: "text" },
19
+ },
20
+ },
21
+ parameters: {
22
+ docs: {
23
+ description: {
24
+ component:
25
+ "A thin layout wrapper that pins a single child to the bottom of the viewport on mobile (≤ 768px) using `position: fixed` and renders in document flow on desktop. Designed to wrap CTAs such as the post-quote Combined Payments checkout button.",
26
+ },
27
+ },
28
+ },
29
+ };
30
+
31
+ const FillerContent = () => (
32
+ <Stack gap="sm">
33
+ {Array.from({ length: 12 }).map((_, i) => (
34
+ <Text key={i}>
35
+ Scroll the canvas on a mobile viewport (≤ 768px) — the Checkout button
36
+ below stays clamped to the bottom of the visible area while content
37
+ scrolls behind it. On desktop it just renders in flow.
38
+ </Text>
39
+ ))}
40
+ </Stack>
41
+ );
42
+
43
+ export const Default: StoryType = {
44
+ render: (args: StickyOnMobileProps) => (
45
+ <Stack gap="lg">
46
+ <FillerContent />
47
+ <StickyOnMobile {...args}>
48
+ <Button type="button" variant="primary">
49
+ Checkout
50
+ </Button>
51
+ </StickyOnMobile>
52
+ </Stack>
53
+ ),
54
+ };
55
+
56
+ export const KeyboardTest: StoryType = {
57
+ parameters: {
58
+ docs: {
59
+ description: {
60
+ story:
61
+ "Manual QA surface for soft-keyboard behaviour. Storybook viewport addon does NOT simulate the keyboard — open this on a real (or simulator) iPhone and Android, focus the input, and observe whether the sticky bar gets covered. iOS Safari is the case the ticket flags for Browserstack.",
62
+ },
63
+ },
64
+ },
65
+ render: (args: StickyOnMobileProps) => (
66
+ <Stack gap="lg">
67
+ <FillerContent />
68
+ <TextField
69
+ label="Tap to open the keyboard"
70
+ name="keyboard-test"
71
+ placeholder="Type anything"
72
+ />
73
+ <FillerContent />
74
+ <StickyOnMobile {...args}>
75
+ <Button type="button" variant="primary">
76
+ Checkout
77
+ </Button>
78
+ </StickyOnMobile>
79
+ </Stack>
80
+ ),
81
+ };
82
+
83
+ export const AsFooterElement: StoryType = {
84
+ render: (args: StickyOnMobileProps) => (
85
+ <Stack gap="lg">
86
+ <FillerContent />
87
+ <StickyOnMobile {...args} elementType="footer">
88
+ <Button type="button" variant="primary">
89
+ Checkout
90
+ </Button>
91
+ </StickyOnMobile>
92
+ </Stack>
93
+ ),
94
+ args: {
95
+ elementType: "footer",
96
+ },
97
+ };
98
+
99
+ export default meta;
@@ -0,0 +1,100 @@
1
+ import { createRef } from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { StickyOnMobile } from ".";
4
+
5
+ const CLASS_NAME = "mobius-sticky-on-mobile";
6
+
7
+ describe("StickyOnMobile", () => {
8
+ it("renders children", () => {
9
+ render(<StickyOnMobile>Sample text</StickyOnMobile>);
10
+ expect(screen.getByText("Sample text")).toBeInTheDocument();
11
+ });
12
+
13
+ it("renders a div by default", () => {
14
+ render(<StickyOnMobile data-testid="bar">x</StickyOnMobile>);
15
+ expect(screen.getByTestId("bar").tagName).toBe("DIV");
16
+ });
17
+
18
+ it("uses the provided elementType", () => {
19
+ render(
20
+ <StickyOnMobile elementType="footer" data-testid="bar">
21
+ x
22
+ </StickyOnMobile>,
23
+ );
24
+ expect(screen.getByTestId("bar").tagName).toBe("FOOTER");
25
+ });
26
+
27
+ it("does not pass elementType through as a DOM attribute", () => {
28
+ render(
29
+ <StickyOnMobile elementType="footer" data-testid="bar">
30
+ x
31
+ </StickyOnMobile>,
32
+ );
33
+ expect(screen.getByTestId("bar")).not.toHaveAttribute(
34
+ "elementtype",
35
+ "footer",
36
+ );
37
+ });
38
+
39
+ it("forwards style attributes", () => {
40
+ render(
41
+ <StickyOnMobile style={{ backgroundColor: "red" }} data-testid="bar">
42
+ x
43
+ </StickyOnMobile>,
44
+ );
45
+ expect(screen.getByTestId("bar")).toHaveStyle({
46
+ "background-color": "rgb(255, 0, 0)",
47
+ });
48
+ });
49
+
50
+ it("spreads HTML attributes onto the element", () => {
51
+ render(
52
+ <StickyOnMobile data-testid="bar" aria-label="payment actions">
53
+ x
54
+ </StickyOnMobile>,
55
+ );
56
+ expect(screen.getByTestId("bar")).toHaveAttribute(
57
+ "aria-label",
58
+ "payment actions",
59
+ );
60
+ });
61
+
62
+ describe("class names", () => {
63
+ it("applies the mobius base class", () => {
64
+ render(<StickyOnMobile data-testid="bar">x</StickyOnMobile>);
65
+ expect(screen.getByTestId("bar")).toHaveClass("mobius");
66
+ });
67
+
68
+ it("applies the mobius-sticky-on-mobile class", () => {
69
+ render(<StickyOnMobile data-testid="bar">x</StickyOnMobile>);
70
+ expect(screen.getByTestId("bar")).toHaveClass(CLASS_NAME);
71
+ });
72
+
73
+ it("merges a custom className", () => {
74
+ render(
75
+ <StickyOnMobile className="my-class" data-testid="bar">
76
+ x
77
+ </StickyOnMobile>,
78
+ );
79
+ expect(screen.getByTestId("bar")).toHaveClass("my-class");
80
+ expect(screen.getByTestId("bar")).toHaveClass(CLASS_NAME);
81
+ });
82
+ });
83
+
84
+ it("forwards ref", () => {
85
+ const ref = createRef<HTMLDivElement>();
86
+ render(<StickyOnMobile ref={ref}>x</StickyOnMobile>);
87
+ expect(ref.current).toBeInstanceOf(HTMLDivElement);
88
+ });
89
+
90
+ describe("breakpoint behaviour", () => {
91
+ // Behaviour at the 768px breakpoint is driven by a static @media query in
92
+ // StickyOnMobile.css. JSDOM does not evaluate @media rules, so these tests
93
+ // verify the *contract*: the class hook is applied unconditionally so the
94
+ // CSS can switch positioning based on viewport width.
95
+ it("applies the sticky class hook regardless of viewport (CSS handles the switch)", () => {
96
+ render(<StickyOnMobile data-testid="bar">x</StickyOnMobile>);
97
+ expect(screen.getByTestId("bar")).toHaveClass(CLASS_NAME);
98
+ });
99
+ });
100
+ });
@@ -0,0 +1,37 @@
1
+ import type {
2
+ RefAttributes,
3
+ ReactNode,
4
+ ElementType,
5
+ CSSProperties,
6
+ HTMLAttributes,
7
+ } from "react";
8
+ import classNames from "classnames/dedupe";
9
+ import "./StickyOnMobile.css";
10
+
11
+ export type StickyOnMobileElementType = HTMLElement;
12
+
13
+ export interface StickyOnMobileProps
14
+ extends
15
+ HTMLAttributes<StickyOnMobileElementType>,
16
+ RefAttributes<StickyOnMobileElementType> {
17
+ children?: ReactNode;
18
+ /** HTML element to render. Defaults to `div`. */
19
+ elementType?: ElementType;
20
+ className?: string;
21
+ style?: CSSProperties;
22
+ }
23
+
24
+ const StickyOnMobile = ({ ref, ...props }: StickyOnMobileProps) => {
25
+ const { elementType: Element = "div", ...otherProps } = props;
26
+
27
+ const classes = classNames(
28
+ "mobius",
29
+ "mobius-sticky-on-mobile",
30
+ otherProps.className,
31
+ );
32
+
33
+ return <Element ref={ref} {...otherProps} className={classes} />;
34
+ };
35
+
36
+ StickyOnMobile.displayName = "StickyOnMobile";
37
+ export { StickyOnMobile };
@@ -0,0 +1 @@
1
+ export * from "./StickyOnMobile";
@@ -34,6 +34,7 @@ export * from "./Segment";
34
34
  export * from "./Select";
35
35
  export * from "./Slider";
36
36
  export * from "./Stack";
37
+ export * from "./StickyOnMobile";
37
38
  export * from "./SVG";
38
39
  export * from "./Switch";
39
40
  export * from "./Table";
@@ -1 +0,0 @@
1
- //# sourceMappingURL=chunk-NOQ27VLY.js.map
@@ -1,92 +0,0 @@
1
- import {
2
- LoqateAddressLookupError
3
- } from "./chunk-EDSRI6SV.js";
4
- import {
5
- __privateAdd,
6
- __privateGet,
7
- __privateSet
8
- } from "./chunk-VHAA22YE.js";
9
-
10
- // src/components/AddressLookup/LoqateAddressLookupService.tsx
11
- var LOQATE_BASE_URL = "https://api.addressy.com/Capture/Interactive";
12
- var LOQATE_FIND_URL = "/Find/v1.00/json3.ws";
13
- var LOQATE_RETRIEVE_URL = "/Retrieve/v1.2/json3.ws";
14
- var DEFAULT_COUNTRIES = ["GB"];
15
- var _baseUrl, _apiKey, _countries, _filters;
16
- var LoqateAddressLookupService = class {
17
- constructor({
18
- baseUrl,
19
- apiKey,
20
- countries,
21
- filters
22
- }) {
23
- /**
24
- * Base URL for the Loqate API
25
- */
26
- __privateAdd(this, _baseUrl);
27
- /**
28
- * API key for the Loqate API
29
- */
30
- __privateAdd(this, _apiKey);
31
- /**
32
- * List of allowed country codes for the Loqate API
33
- * 2 or 3 character ISO country codes
34
- */
35
- __privateAdd(this, _countries);
36
- /**
37
- * Optional filters for the Loqate API
38
- * E.g., { AdministrativeArea: "CA", PostalCode: "90210" }
39
- */
40
- __privateAdd(this, _filters);
41
- __privateSet(this, _apiKey, apiKey);
42
- __privateSet(this, _baseUrl, baseUrl || LOQATE_BASE_URL);
43
- __privateSet(this, _countries, countries || DEFAULT_COUNTRIES);
44
- __privateSet(this, _filters, filters);
45
- }
46
- fetchFromApi(url) {
47
- return fetch(`${__privateGet(this, _baseUrl)}${url}`).then((response) => response.json()).then((json) => {
48
- if (json.Items?.some((item) => item.Error)) {
49
- throw new LoqateAddressLookupError(json);
50
- }
51
- return json;
52
- });
53
- }
54
- /**
55
- * Builds the Filters query parameter for Loqate API requests.
56
- * - Filter keys (e.g., "AdministrativeArea", "PostalCode") are predefined by Loqate API (no need to encode)
57
- * - Filter values (e.g., "New York", "90210") contain user input that may have special characters (need encoding)
58
- *
59
- * @returns Empty string if no filters, otherwise "&Filters=key1:value1&key2:value2" (Loqate's expected format for Filters)
60
- */
61
- buildFiltersQuery() {
62
- if (!__privateGet(this, _filters) || Object.keys(__privateGet(this, _filters)).length === 0) {
63
- return "";
64
- }
65
- const encodedFilters = Object.entries(__privateGet(this, _filters)).map(([key, value]) => `${key}:${encodeURIComponent(value)}`).join("&");
66
- return `&Filters=${encodedFilters}`;
67
- }
68
- search(searchTerm) {
69
- let url = `${LOQATE_FIND_URL}?Key=${__privateGet(this, _apiKey)}&Text=${searchTerm}&Countries=${__privateGet(this, _countries)?.join(",")}`;
70
- url += this.buildFiltersQuery();
71
- return this.fetchFromApi(url);
72
- }
73
- findById(id) {
74
- let url = `${LOQATE_FIND_URL}?Key=${__privateGet(this, _apiKey)}&Container=${id}&Countries=${__privateGet(this, _countries)?.join(",")}`;
75
- url += this.buildFiltersQuery();
76
- return this.fetchFromApi(url);
77
- }
78
- async get(id) {
79
- const url = `${LOQATE_RETRIEVE_URL}?Key=${__privateGet(this, _apiKey)}&Id=${id}`;
80
- const response = await this.fetchFromApi(url);
81
- return response.Items[0];
82
- }
83
- };
84
- _baseUrl = new WeakMap();
85
- _apiKey = new WeakMap();
86
- _countries = new WeakMap();
87
- _filters = new WeakMap();
88
-
89
- export {
90
- LoqateAddressLookupService
91
- };
92
- //# sourceMappingURL=chunk-VVL4B2KD.js.map