@madecki/ui 2.0.0 → 2.1.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/CHANGELOG.md +7 -0
- package/README.md +73 -0
- package/cursor-rule-template.md +1 -1
- package/dist/index.cjs +149 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +36 -2
- package/dist/index.d.ts +36 -2
- package/dist/index.js +148 -8
- package/dist/index.js.map +1 -1
- package/llm-context.md +2 -0
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [2.1.0](https://github.com/madecki/ui/compare/v2.0.0...v2.1.0) (2026-04-08)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* add DetailsPanel native details/summary disclosure ([d1d24d9](https://github.com/madecki/ui/commit/d1d24d989537fe1845aeb5059b3f59d745357c23))
|
|
6
|
+
* add Toast with placement, auto-close, and variants ([5874709](https://github.com/madecki/ui/commit/587470962cf265eafe8cc9659b78b073f6ef1756))
|
|
7
|
+
|
|
1
8
|
## [2.0.0](https://github.com/madecki/ui/compare/v1.5.0...v2.0.0) (2026-04-08)
|
|
2
9
|
|
|
3
10
|
### ⚠ BREAKING CHANGES
|
package/README.md
CHANGED
|
@@ -424,6 +424,47 @@ Full-screen loading overlay.
|
|
|
424
424
|
<SpinnerOverlay isVisible={isLoading} />
|
|
425
425
|
```
|
|
426
426
|
|
|
427
|
+
#### Toast
|
|
428
|
+
|
|
429
|
+
Fixed-position message in any screen corner, with configurable auto-dismiss (milliseconds) and a close control. Border colors match semantic tokens (`info` uses the same blue accent as `ContentBox`).
|
|
430
|
+
|
|
431
|
+
```tsx
|
|
432
|
+
import { Toast } from "@madecki/ui";
|
|
433
|
+
import { useState } from "react";
|
|
434
|
+
|
|
435
|
+
function Example() {
|
|
436
|
+
const [toastKey, setToastKey] = useState(0);
|
|
437
|
+
|
|
438
|
+
return (
|
|
439
|
+
<>
|
|
440
|
+
<button type="button" onClick={() => setToastKey((n) => n + 1)}>
|
|
441
|
+
Save
|
|
442
|
+
</button>
|
|
443
|
+
{toastKey > 0 ? (
|
|
444
|
+
<Toast
|
|
445
|
+
key={toastKey}
|
|
446
|
+
variant="success"
|
|
447
|
+
placement="bottom-right"
|
|
448
|
+
autoCloseMs={4000}
|
|
449
|
+
onClose={() => setToastKey(0)}
|
|
450
|
+
>
|
|
451
|
+
<p className="text-white">Saved.</p>
|
|
452
|
+
</Toast>
|
|
453
|
+
) : null}
|
|
454
|
+
</>
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
| Prop | Type | Default | Description |
|
|
460
|
+
|------|------|---------|-------------|
|
|
461
|
+
| `children` | `ReactNode` | (required) | Message body (use `Text` / markup as needed). |
|
|
462
|
+
| `variant` | `"info"` \| `"success"` \| `"danger"` | `"info"` | Semantic style (border / live region). |
|
|
463
|
+
| `placement` | `"top-left"` \| `"top-right"` \| `"bottom-left"` \| `"bottom-right"` | `"bottom-right"` | Viewport corner (`fixed`, offset with spacing token `5`). |
|
|
464
|
+
| `autoCloseMs` | `number` | `5000` | Auto-dismiss delay in ms; use `0` to disable. |
|
|
465
|
+
| `onClose` | `() => void` | — | Fired when the toast is dismissed (timer or close control). |
|
|
466
|
+
| `className` | `string` | `""` | Extra classes on the root element. |
|
|
467
|
+
|
|
427
468
|
### Content Components
|
|
428
469
|
|
|
429
470
|
#### BlockQuote
|
|
@@ -452,6 +493,37 @@ import { ContentBox, Info, Warning } from "@madecki/ui";
|
|
|
452
493
|
</ContentBox>
|
|
453
494
|
```
|
|
454
495
|
|
|
496
|
+
#### DetailsPanel
|
|
497
|
+
|
|
498
|
+
Collapsible panel using native **`<details>`** and **`<summary>`** (accessible disclosure), with the same border variants as `ContentBox`.
|
|
499
|
+
|
|
500
|
+
```tsx
|
|
501
|
+
import { DetailsPanel, Info } from "@madecki/ui";
|
|
502
|
+
|
|
503
|
+
<DetailsPanel variant="info" summary="Show more">
|
|
504
|
+
<p className="text-white">Extra content appears here when expanded.</p>
|
|
505
|
+
</DetailsPanel>
|
|
506
|
+
|
|
507
|
+
<DetailsPanel
|
|
508
|
+
variant="warning"
|
|
509
|
+
icon={<Info />}
|
|
510
|
+
defaultOpen
|
|
511
|
+
summary="Important details"
|
|
512
|
+
>
|
|
513
|
+
<p className="text-white">Initially open; still toggles like a normal details element.</p>
|
|
514
|
+
</DetailsPanel>
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
| Prop | Type | Default | Description |
|
|
518
|
+
|------|------|---------|-------------|
|
|
519
|
+
| `summary` | `ReactNode` | (required) | Content of the `<summary>` row (click to toggle). |
|
|
520
|
+
| `children` | `ReactNode` | (required) | Panel body shown when open. |
|
|
521
|
+
| `variant` | `"info"` \| `"warning"` \| `"success"` \| `"danger"` | `"info"` | Border color (matches `ContentBox`). |
|
|
522
|
+
| `icon` | `ReactNode` | — | Optional icon before the summary text. |
|
|
523
|
+
| `defaultOpen` | `boolean` | `false` | Initial open state; toggling still uses native `<details>` behavior (open state is synced in React so `defaultOpen` works reliably). |
|
|
524
|
+
| `className` | `string` | `""` | Extra classes on `<details>`. |
|
|
525
|
+
| `id` | `string` | — | `id` on `<details>`. |
|
|
526
|
+
|
|
455
527
|
#### Hr
|
|
456
528
|
|
|
457
529
|
Styled horizontal rule.
|
|
@@ -589,6 +661,7 @@ There is **no** separate manual changelog step: if it is not described in a merg
|
|
|
589
661
|
This project uses [Conventional Commits](https://www.conventionalcommits.org/) enforced by [commitlint](https://commitlint.js.org/):
|
|
590
662
|
|
|
591
663
|
- **Locally:** Husky runs **`commitlint`** on the **`commit-msg`** hook, so **`git commit` fails** if the message does not follow the rules (bypass only with **`git commit --no-verify`**).
|
|
664
|
+
- **Locally:** Husky runs **`npm run precheck`** on **`pre-push`** (typecheck, lint, build, tests, Storybook build — same as CI/Release). Bypass with **`git push --no-verify`** if you must.
|
|
592
665
|
- **On `main`:** CI runs commitlint on **every push** for the commits in that push, so bad messages are caught even if someone skipped the hook.
|
|
593
666
|
|
|
594
667
|
**Format:** `type(scope?): description`
|
package/cursor-rule-template.md
CHANGED
|
@@ -44,7 +44,7 @@ This file contains:
|
|
|
44
44
|
## Import Pattern
|
|
45
45
|
|
|
46
46
|
\`\`\`tsx
|
|
47
|
-
import { Container, Stack, Heading, Text, Button } from "@madecki/ui";
|
|
47
|
+
import { Container, Stack, Heading, Text, Button, DetailsPanel, Toast } from "@madecki/ui";
|
|
48
48
|
import { Heart, Info } from "@madecki/ui";
|
|
49
49
|
\`\`\`
|
|
50
50
|
|
package/dist/index.cjs
CHANGED
|
@@ -1014,6 +1014,93 @@ var SpinnerOverlay = ({
|
|
|
1014
1014
|
}
|
|
1015
1015
|
);
|
|
1016
1016
|
};
|
|
1017
|
+
|
|
1018
|
+
// src/components/contentBoxVariants.ts
|
|
1019
|
+
var contentBoxVariantBorderClasses = {
|
|
1020
|
+
info: "border-blue",
|
|
1021
|
+
warning: "border-warning",
|
|
1022
|
+
success: "border-success",
|
|
1023
|
+
danger: "border-danger"
|
|
1024
|
+
};
|
|
1025
|
+
var placementClassNames = {
|
|
1026
|
+
"top-left": "top-5 left-5",
|
|
1027
|
+
"top-right": "top-5 right-5",
|
|
1028
|
+
"bottom-left": "bottom-5 left-5",
|
|
1029
|
+
"bottom-right": "bottom-5 right-5"
|
|
1030
|
+
};
|
|
1031
|
+
function CloseIcon({ className = "" }) {
|
|
1032
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1033
|
+
"svg",
|
|
1034
|
+
{
|
|
1035
|
+
width: 16,
|
|
1036
|
+
height: 16,
|
|
1037
|
+
viewBox: "0 0 16 16",
|
|
1038
|
+
fill: "none",
|
|
1039
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1040
|
+
className,
|
|
1041
|
+
"aria-hidden": true,
|
|
1042
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1043
|
+
"path",
|
|
1044
|
+
{
|
|
1045
|
+
d: "M4 4L12 12M12 4L4 12",
|
|
1046
|
+
stroke: "currentColor",
|
|
1047
|
+
strokeWidth: "2",
|
|
1048
|
+
strokeLinecap: "round"
|
|
1049
|
+
}
|
|
1050
|
+
)
|
|
1051
|
+
}
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
var Toast = ({
|
|
1055
|
+
children,
|
|
1056
|
+
variant = "info",
|
|
1057
|
+
placement = "bottom-right",
|
|
1058
|
+
autoCloseMs = 5e3,
|
|
1059
|
+
onClose,
|
|
1060
|
+
className = ""
|
|
1061
|
+
}) => {
|
|
1062
|
+
const [open, setOpen] = react.useState(true);
|
|
1063
|
+
const onCloseRef = react.useRef(onClose);
|
|
1064
|
+
onCloseRef.current = onClose;
|
|
1065
|
+
const dismiss = react.useCallback(() => {
|
|
1066
|
+
setOpen(false);
|
|
1067
|
+
onCloseRef.current?.();
|
|
1068
|
+
}, []);
|
|
1069
|
+
react.useEffect(() => {
|
|
1070
|
+
if (!open || autoCloseMs <= 0) {
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
const id = window.setTimeout(dismiss, autoCloseMs);
|
|
1074
|
+
return () => window.clearTimeout(id);
|
|
1075
|
+
}, [open, autoCloseMs, dismiss]);
|
|
1076
|
+
if (!open) {
|
|
1077
|
+
return null;
|
|
1078
|
+
}
|
|
1079
|
+
const border = contentBoxVariantBorderClasses[variant];
|
|
1080
|
+
const live = variant === "danger" ? "assertive" : "polite";
|
|
1081
|
+
const role = variant === "danger" ? "alert" : "status";
|
|
1082
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1083
|
+
"div",
|
|
1084
|
+
{
|
|
1085
|
+
role,
|
|
1086
|
+
"aria-live": live,
|
|
1087
|
+
className: `fixed z-50 flex max-w-sm gap-3 rounded-md border-2 bg-darkgray p-5 text-md text-white shadow-lg dark:bg-gray ${placementClassNames[placement]} ${border} ${className}`,
|
|
1088
|
+
children: [
|
|
1089
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children }),
|
|
1090
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1091
|
+
"button",
|
|
1092
|
+
{
|
|
1093
|
+
type: "button",
|
|
1094
|
+
onClick: dismiss,
|
|
1095
|
+
className: "shrink-0 rounded-sm p-2 text-icongray transition-colors hover:bg-white/10 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue",
|
|
1096
|
+
"aria-label": "Close",
|
|
1097
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {})
|
|
1098
|
+
}
|
|
1099
|
+
)
|
|
1100
|
+
]
|
|
1101
|
+
}
|
|
1102
|
+
);
|
|
1103
|
+
};
|
|
1017
1104
|
var BlockQuote = ({ children, className = "" }) => {
|
|
1018
1105
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1019
1106
|
"blockquote",
|
|
@@ -1027,12 +1114,6 @@ var Hr = ({ className = "" }) => {
|
|
|
1027
1114
|
const defaultClassNames = "border border-gray my-4";
|
|
1028
1115
|
return /* @__PURE__ */ jsxRuntime.jsx("hr", { className: `${defaultClassNames} ${className}` });
|
|
1029
1116
|
};
|
|
1030
|
-
var variantStyles = {
|
|
1031
|
-
info: "border-blue",
|
|
1032
|
-
warning: "border-warning",
|
|
1033
|
-
success: "border-success",
|
|
1034
|
-
danger: "border-danger"
|
|
1035
|
-
};
|
|
1036
1117
|
var ContentBox = ({
|
|
1037
1118
|
children,
|
|
1038
1119
|
variant = "info",
|
|
@@ -1042,7 +1123,7 @@ var ContentBox = ({
|
|
|
1042
1123
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1043
1124
|
"div",
|
|
1044
1125
|
{
|
|
1045
|
-
className: `relative border rounded-md my-9 ${
|
|
1126
|
+
className: `relative border rounded-md my-9 ${contentBoxVariantBorderClasses[variant]} ${className}`,
|
|
1046
1127
|
children: [
|
|
1047
1128
|
icon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute flex items-center justify-center p-3 -top-6 right-8 bg-primary border border-darkgray", children: icon }),
|
|
1048
1129
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "m-7", children })
|
|
@@ -1050,6 +1131,65 @@ var ContentBox = ({
|
|
|
1050
1131
|
}
|
|
1051
1132
|
);
|
|
1052
1133
|
};
|
|
1134
|
+
function ChevronDown2({ className = "" }) {
|
|
1135
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1136
|
+
"svg",
|
|
1137
|
+
{
|
|
1138
|
+
width: 20,
|
|
1139
|
+
height: 20,
|
|
1140
|
+
viewBox: "0 0 20 20",
|
|
1141
|
+
fill: "none",
|
|
1142
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1143
|
+
className,
|
|
1144
|
+
"aria-hidden": true,
|
|
1145
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1146
|
+
"path",
|
|
1147
|
+
{
|
|
1148
|
+
d: "M5 7.5L10 12.5L15 7.5",
|
|
1149
|
+
stroke: "currentColor",
|
|
1150
|
+
strokeWidth: "1.5",
|
|
1151
|
+
strokeLinecap: "round",
|
|
1152
|
+
strokeLinejoin: "round"
|
|
1153
|
+
}
|
|
1154
|
+
)
|
|
1155
|
+
}
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
var DetailsPanel = ({
|
|
1159
|
+
summary,
|
|
1160
|
+
children,
|
|
1161
|
+
variant = "info",
|
|
1162
|
+
icon,
|
|
1163
|
+
defaultOpen = false,
|
|
1164
|
+
className = "",
|
|
1165
|
+
id
|
|
1166
|
+
}) => {
|
|
1167
|
+
const border = contentBoxVariantBorderClasses[variant];
|
|
1168
|
+
const [open, setOpen] = react.useState(defaultOpen);
|
|
1169
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1170
|
+
"details",
|
|
1171
|
+
{
|
|
1172
|
+
id,
|
|
1173
|
+
className: `group relative border rounded-md my-9 ${border} ${className}`,
|
|
1174
|
+
open,
|
|
1175
|
+
onToggle: (e) => setOpen(e.currentTarget.open),
|
|
1176
|
+
children: [
|
|
1177
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1178
|
+
"summary",
|
|
1179
|
+
{
|
|
1180
|
+
className: "flex cursor-pointer list-none items-center gap-3 p-7 text-white outline-none transition-colors hover:bg-darkgray/40 dark:hover:bg-white/5 focus-visible:ring-2 focus-visible:ring-blue group-open:pb-3 [&::-webkit-details-marker]:hidden",
|
|
1181
|
+
children: [
|
|
1182
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChevronDown2, { className: "size-5 shrink-0 text-icongray transition-transform group-open:rotate-180" }),
|
|
1183
|
+
icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex shrink-0 items-center", children: icon }) : null,
|
|
1184
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 text-md font-medium text-white dark:text-white", children: summary })
|
|
1185
|
+
]
|
|
1186
|
+
}
|
|
1187
|
+
),
|
|
1188
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-gray px-7 pb-7 pt-3 dark:border-gray", children })
|
|
1189
|
+
]
|
|
1190
|
+
}
|
|
1191
|
+
);
|
|
1192
|
+
};
|
|
1053
1193
|
var sizeStyles2 = {
|
|
1054
1194
|
sm: "max-w-screen-sm",
|
|
1055
1195
|
md: "max-w-screen-md",
|
|
@@ -1587,6 +1727,7 @@ exports.Button = Button;
|
|
|
1587
1727
|
exports.ButtonTransparent = ButtonTransparent;
|
|
1588
1728
|
exports.Container = Container;
|
|
1589
1729
|
exports.ContentBox = ContentBox;
|
|
1730
|
+
exports.DetailsPanel = DetailsPanel;
|
|
1590
1731
|
exports.GradientButton = GradientButton;
|
|
1591
1732
|
exports.Grid = Grid;
|
|
1592
1733
|
exports.GridItem = GridItem;
|
|
@@ -1608,6 +1749,7 @@ exports.Tabs = Tabs;
|
|
|
1608
1749
|
exports.Tag = Tag;
|
|
1609
1750
|
exports.Text = Text;
|
|
1610
1751
|
exports.Textarea = Textarea;
|
|
1752
|
+
exports.Toast = Toast;
|
|
1611
1753
|
exports.TwitterIcon = TwitterIcon;
|
|
1612
1754
|
exports.Warning = Warning;
|
|
1613
1755
|
//# sourceMappingURL=index.cjs.map
|