hrnet-modal-react-dark 0.0.0-stage → 1.0.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.
- package/README.md +71 -2
- package/dist/Modal.d.ts +27 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +56 -0
- package/dist/style.css +3 -0
- package/package.json +47 -4
package/README.md
CHANGED
|
@@ -1,3 +1,72 @@
|
|
|
1
|
-
#
|
|
1
|
+
# hrnet-modal-react-dark
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Accessible React confirmation modal, built for **HRnet** (WealthHealth) to replace the jQuery plugin [`jquery.modal`](https://github.com/kylefox/jquery-modal).
|
|
4
|
+
|
|
5
|
+
- No jQuery, no runtime dependency: only `react` and `react-dom` as peer dependencies
|
|
6
|
+
- Function component with hooks, written in TypeScript (types included)
|
|
7
|
+
- Closes with the **×** button, a click on the overlay, or the **Escape** key
|
|
8
|
+
- Locks page scroll while open
|
|
9
|
+
- `role="dialog"` and `aria-modal="true"`; the title is the accessible name
|
|
10
|
+
- Dark theme, styles shipped as one small CSS file (works with or without Tailwind)
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install hrnet-modal-react-dark
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Requires React 18 or later.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
Import the component **and its stylesheet** once (for example in `main.tsx`):
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { useState } from 'react'
|
|
26
|
+
import Modal from 'hrnet-modal-react-dark'
|
|
27
|
+
import 'hrnet-modal-react-dark/style.css'
|
|
28
|
+
|
|
29
|
+
export default function CreateEmployee() {
|
|
30
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
<button onClick={() => setIsOpen(true)}>Save</button>
|
|
35
|
+
|
|
36
|
+
<Modal
|
|
37
|
+
isOpen={isOpen}
|
|
38
|
+
onClose={() => setIsOpen(false)}
|
|
39
|
+
title="Employee created!"
|
|
40
|
+
description="The employee has been added to the directory."
|
|
41
|
+
actions={<button onClick={() => setIsOpen(false)}>Close</button>}
|
|
42
|
+
/>
|
|
43
|
+
</>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The modal is **controlled**: the parent keeps the `isOpen` state and `onClose` updates it.
|
|
49
|
+
|
|
50
|
+
## Props
|
|
51
|
+
|
|
52
|
+
| Prop | Type | Required | Description |
|
|
53
|
+
| --- | --- | --- | --- |
|
|
54
|
+
| `isOpen` | `boolean` | yes | Shows the modal when `true`. When `false` nothing is rendered. |
|
|
55
|
+
| `onClose` | `() => void` | yes | Called when the user closes the modal (× button, overlay click or Escape). Usually sets `isOpen` to `false`. |
|
|
56
|
+
| `title` | `string` | yes | Heading of the modal, also used as the dialog's accessible name. |
|
|
57
|
+
| `description` | `string \| null` | no | Text displayed under the title. |
|
|
58
|
+
| `actions` | `ReactNode` | no | Footer content, typically one or more buttons. |
|
|
59
|
+
|
|
60
|
+
## Development
|
|
61
|
+
|
|
62
|
+
The source lives in `src/`. The build (Vite library mode + TypeScript declarations) outputs to `dist/`:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm run build
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`dist/index.js` is an ES module, `dist/style.css` holds the styles, `dist/*.d.ts` the types.
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
MIT
|
package/dist/Modal.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export type ModalProps = {
|
|
3
|
+
/**
|
|
4
|
+
* Controls visibility. When `false` the modal renders nothing.
|
|
5
|
+
* The parent owns this state (controlled component).
|
|
6
|
+
*/
|
|
7
|
+
isOpen: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Called when the user asks to close the modal: close button (×),
|
|
10
|
+
* click on the dark overlay, or Escape key. Usually sets `isOpen` to `false`.
|
|
11
|
+
*/
|
|
12
|
+
onClose: () => void;
|
|
13
|
+
/** Main heading of the dialog. Also used as the accessible name of the dialog. */
|
|
14
|
+
title: string;
|
|
15
|
+
/** Optional text displayed under the title. */
|
|
16
|
+
description?: string | null;
|
|
17
|
+
/** Optional footer content, typically one or more buttons. */
|
|
18
|
+
actions?: ReactNode;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Confirmation modal (replacement for the jQuery `jquery.modal.js` plugin).
|
|
22
|
+
*
|
|
23
|
+
* - Renders nothing while `isOpen` is false.
|
|
24
|
+
* - Closes on Escape, on overlay click and with the close button.
|
|
25
|
+
* - Locks page scroll while open and restores it on close.
|
|
26
|
+
*/
|
|
27
|
+
export default function Modal({ isOpen, onClose, title, description, actions }: ModalProps): import("react").JSX.Element | null;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useEffect as e } from "react";
|
|
2
|
+
import { jsx as t, jsxs as n } from "react/jsx-runtime";
|
|
3
|
+
// src/Modal.tsx
|
|
4
|
+
function r({ isOpen: r, onClose: i, title: a, description: o, actions: s }) {
|
|
5
|
+
return e(() => {
|
|
6
|
+
if (!r) return;
|
|
7
|
+
let e = (e) => {
|
|
8
|
+
e.key === "Escape" && i();
|
|
9
|
+
}, t = document.body.style.overflow;
|
|
10
|
+
return document.body.style.overflow = "hidden", window.addEventListener("keydown", e), () => {
|
|
11
|
+
document.body.style.overflow = t, window.removeEventListener("keydown", e);
|
|
12
|
+
};
|
|
13
|
+
}, [r, i]), r ? /* @__PURE__ */ t("div", {
|
|
14
|
+
className: "fixed inset-0 z-50 flex items-center justify-center bg-slate-950/75 p-4",
|
|
15
|
+
onClick: i,
|
|
16
|
+
role: "presentation",
|
|
17
|
+
children: /* @__PURE__ */ n("div", {
|
|
18
|
+
className: "w-full max-w-md rounded-2xl border border-slate-700 bg-slate-900 p-5 shadow-2xl shadow-slate-950/70",
|
|
19
|
+
role: "dialog",
|
|
20
|
+
"aria-modal": "true",
|
|
21
|
+
"aria-labelledby": "modal-title",
|
|
22
|
+
onClick: (e) => e.stopPropagation(),
|
|
23
|
+
children: [
|
|
24
|
+
/* @__PURE__ */ n("div", {
|
|
25
|
+
className: "mb-4 flex items-center justify-between",
|
|
26
|
+
children: [/* @__PURE__ */ t("div", {
|
|
27
|
+
className: "flex h-11 w-11 items-center justify-center rounded-xl bg-emerald-500/15 text-xl text-emerald-300",
|
|
28
|
+
children: "✓"
|
|
29
|
+
}), /* @__PURE__ */ t("button", {
|
|
30
|
+
type: "button",
|
|
31
|
+
className: "inline-flex h-9 w-9 items-center justify-center rounded-lg border border-slate-700 bg-slate-800 text-lg text-slate-200 transition hover:bg-slate-700",
|
|
32
|
+
onClick: i,
|
|
33
|
+
"aria-label": "Close modal",
|
|
34
|
+
children: "×"
|
|
35
|
+
})]
|
|
36
|
+
}),
|
|
37
|
+
/* @__PURE__ */ n("div", {
|
|
38
|
+
className: "text-center",
|
|
39
|
+
children: [/* @__PURE__ */ t("h2", {
|
|
40
|
+
id: "modal-title",
|
|
41
|
+
className: "text-2xl font-semibold text-white",
|
|
42
|
+
children: a
|
|
43
|
+
}), o ? /* @__PURE__ */ t("p", {
|
|
44
|
+
className: "mt-2 text-sm leading-6 text-slate-300",
|
|
45
|
+
children: o
|
|
46
|
+
}) : null]
|
|
47
|
+
}),
|
|
48
|
+
s ? /* @__PURE__ */ t("div", {
|
|
49
|
+
className: "mt-5 flex justify-end",
|
|
50
|
+
children: s
|
|
51
|
+
}) : null
|
|
52
|
+
]
|
|
53
|
+
})
|
|
54
|
+
}) : null;
|
|
55
|
+
}
|
|
56
|
+
export { r as Modal, r as default };
|
package/dist/style.css
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/*! tailwindcss v4.3.5 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:block))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}@layer theme{:root,:host{--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-500:oklch(69.6% .17 162.48);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-slate-900:oklch(20.8% .042 265.755);--color-slate-950:oklch(12.9% .042 264.695);--color-white:#fff;--spacing:.25rem;--container-md:28rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-semibold:600;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1)}}@layer base,components;@layer utilities{.fixed{position:fixed}.inset-0{inset:0}.z-50{z-index:50}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-5{margin-top:calc(var(--spacing) * 5)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.flex{display:flex}.hidden{display:none}.inline-flex{display:inline-flex}.h-9{height:calc(var(--spacing) * 9)}.h-11{height:calc(var(--spacing) * 11)}.w-9{width:calc(var(--spacing) * 9)}.w-11{width:calc(var(--spacing) * 11)}.w-full{width:100%}.max-w-md{max-width:var(--container-md)}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-slate-700{border-color:var(--color-slate-700)}.bg-emerald-500\/15{background-color:#00bb7f26}@supports (color:color-mix(in lab, red, red)){.bg-emerald-500\/15{background-color:color-mix(in oklab, var(--color-emerald-500) 15%, transparent)}}.bg-slate-800{background-color:var(--color-slate-800)}.bg-slate-900{background-color:var(--color-slate-900)}.bg-slate-950\/75{background-color:#020618bf}@supports (color:color-mix(in lab, red, red)){.bg-slate-950\/75{background-color:color-mix(in oklab, var(--color-slate-950) 75%, transparent)}}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.text-center{text-align:center}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.leading-6{--tw-leading:calc(var(--spacing) * 6);line-height:calc(var(--spacing) * 6)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.text-emerald-300{color:var(--color-emerald-300)}.text-slate-200{color:var(--color-slate-200)}.text-slate-300{color:var(--color-slate-300)}.text-white{color:var(--color-white)}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-slate-950\/70{--tw-shadow-color:#020618b3}@supports (color:color-mix(in lab, red, red)){.shadow-slate-950\/70{--tw-shadow-color:color-mix(in oklab, color-mix(in oklab, var(--color-slate-950) 70%, transparent) var(--tw-shadow-alpha), transparent)}}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (hover:hover){.hover\:bg-slate-700:hover{background-color:var(--color-slate-700)}}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}
|
|
3
|
+
/*$vite$:1*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hrnet-modal-react-dark",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Accessible React confirmation modal for HRnet (replacement for the jQuery jquery.modal plugin).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./style.css": "./dist/style.css"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"sideEffects": [
|
|
20
|
+
"**/*.css",
|
|
21
|
+
"./dist/index.js"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "vite build && tsc -p tsconfig.json",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"react",
|
|
29
|
+
"modal",
|
|
30
|
+
"dialog",
|
|
31
|
+
"hrnet"
|
|
32
|
+
],
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"react": ">=18.0.0",
|
|
36
|
+
"react-dom": ">=18.0.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@tailwindcss/vite": "^4.3.3",
|
|
40
|
+
"@types/react": "^19.2.17",
|
|
41
|
+
"@types/react-dom": "^19.2.3",
|
|
42
|
+
"@vitejs/plugin-react": "^6.0.4",
|
|
43
|
+
"react": "^19.2.8",
|
|
44
|
+
"react-dom": "^19.2.8",
|
|
45
|
+
"tailwindcss": "^4.3.3",
|
|
46
|
+
"typescript": "^7.0.2",
|
|
47
|
+
"vite": "^8.2.0"
|
|
48
|
+
}
|
|
49
|
+
}
|