@unitedstatespowersquadrons/components 1.1.1 → 1.1.3

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.
package/Readme.md CHANGED
@@ -16,6 +16,30 @@ Install dependencies with
16
16
  yarn
17
17
  ```
18
18
 
19
+ ## Mounting Components
20
+
21
+ Components can be mounted by passing them into the included `mount` function in
22
+ your application asset root.
23
+
24
+ ```tsx
25
+ mount({
26
+ ActionButton,
27
+ });
28
+ ```
29
+
30
+ This will search the DOM for elements matching the following pattern, and
31
+ attempt to render the corresponding component at that location.
32
+
33
+ ```html
34
+ <div data-react-component="Namespace.Component" data-props="{}"></div>
35
+ ```
36
+
37
+ In Rails, you can generate this element with the following helper call:
38
+
39
+ ```rb
40
+ content_tag(:div, '', data: { 'react-component' => component_name, props: props.to_json })
41
+ ```
42
+
19
43
  ## Toasts
20
44
 
21
45
  More detailed information is available for [Toasts](Toasts/Readme.md) usage.
package/index.tsx CHANGED
@@ -1,3 +1,4 @@
1
+ export { default as mount } from "./mount";
1
2
  export * from "./Reducer";
2
3
 
3
4
  export { default as FontAwesomeIcon } from "./FontAwesomeIcon";
package/mount.tsx ADDED
@@ -0,0 +1,54 @@
1
+ import React from "react";
2
+ import ReactDOM from "react-dom/client";
3
+
4
+ type Components = Record<string, React.ElementType>;
5
+ type NestedComponents = Record<string, React.ElementType | Components>;
6
+
7
+ const findComponent = (
8
+ components: NestedComponents,
9
+ componentName: string
10
+ ): React.ElementType | undefined => {
11
+ let current: NestedComponents | React.ElementType = components;
12
+
13
+ // Walk through the provided component name, filtering the defined components
14
+ if (/\./.exec(componentName)) {
15
+ const keys = componentName.split(".");
16
+ componentName = keys.pop()!; // Reserve last key
17
+
18
+ for (const key of keys) {
19
+ if (typeof current === "object" && key in current) {
20
+ current = current[key]!;
21
+ } else {
22
+ return; // Short-circuit: lookup chain broken
23
+ }
24
+ }
25
+ }
26
+
27
+ // Return the specified component
28
+ return (current as Components)[componentName];
29
+ };
30
+
31
+ export default function mount(components: NestedComponents): void {
32
+ document.addEventListener("DOMContentLoaded", () => {
33
+ const mountPoints = document.querySelectorAll("[data-react-component]");
34
+ mountPoints.forEach(mountPoint => {
35
+ const { dataset } = mountPoint as HTMLElement;
36
+ const componentName = dataset["reactComponent"];
37
+ if (componentName) {
38
+ const Component = findComponent(components, componentName);
39
+ if (Component) {
40
+ const props = JSON.parse(dataset["props"] as string) as object;
41
+ const root = ReactDOM.createRoot(mountPoint);
42
+ root.render(<Component {...props} />);
43
+ } else {
44
+ // eslint-disable-next-line no-console
45
+ console.warn(
46
+ "WARNING: No component found for: ",
47
+ dataset["reactComponent"],
48
+ components
49
+ );
50
+ }
51
+ }
52
+ });
53
+ });
54
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unitedstatespowersquadrons/components",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "USPS shared React components library",
5
5
  "main": "index.tsx",
6
6
  "scripts": {
@@ -20,6 +20,7 @@
20
20
  "classnames": "^2.5.1",
21
21
  "immer": "^10.1.1",
22
22
  "react": "^19.1.0",
23
+ "react-dom": "^19.1.0",
23
24
  "react-jss": "^10.10.0",
24
25
  "typescript": "^5.8.3",
25
26
  "use-immer": "^0.11.0"
@@ -27,6 +28,7 @@
27
28
  "devDependencies": {
28
29
  "@stylistic/eslint-plugin": "^5.1.0",
29
30
  "@types/react": "^19.1.8",
31
+ "@types/react-dom": "^19.1.6",
30
32
  "eslint": "^9.30.1",
31
33
  "eslint-plugin-import": "^2.32.0",
32
34
  "eslint-plugin-import-newlines": "^1.4.0",