@mythicalos/react-ui 0.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/LICENSE +201 -0
- package/NOTICE +5 -0
- package/README.md +78 -0
- package/dist/Avatar.d.ts +5 -0
- package/dist/Avatar.js +5 -0
- package/dist/Banner.d.ts +10 -0
- package/dist/Banner.js +7 -0
- package/dist/Button.d.ts +14 -0
- package/dist/Button.js +8 -0
- package/dist/Card.d.ts +8 -0
- package/dist/Card.js +5 -0
- package/dist/Chip.d.ts +9 -0
- package/dist/Chip.js +7 -0
- package/dist/ConfirmDialog.d.ts +25 -0
- package/dist/ConfirmDialog.js +46 -0
- package/dist/EmptyState.d.ts +8 -0
- package/dist/EmptyState.js +15 -0
- package/dist/Gauge.d.ts +9 -0
- package/dist/Gauge.js +23 -0
- package/dist/Input.d.ts +31 -0
- package/dist/Input.js +39 -0
- package/dist/MaskedSecretInput.d.ts +15 -0
- package/dist/MaskedSecretInput.js +32 -0
- package/dist/SearchInput.d.ts +8 -0
- package/dist/SearchInput.js +6 -0
- package/dist/StatusLine.d.ts +8 -0
- package/dist/StatusLine.js +7 -0
- package/dist/Toast.d.ts +18 -0
- package/dist/Toast.js +51 -0
- package/dist/hooks.d.ts +21 -0
- package/dist/hooks.js +136 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +22 -0
- package/dist/toast-context.d.ts +4 -0
- package/dist/toast-context.js +14 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or Derivative
|
|
95
|
+
Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work, excluding
|
|
103
|
+
those notices that do not pertain to any part of the Derivative
|
|
104
|
+
Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and do
|
|
117
|
+
not modify the License. You may add Your own attribution notices
|
|
118
|
+
within Derivative Works that You distribute, alongside or as an
|
|
119
|
+
addendum to the NOTICE text from the Work, provided that such
|
|
120
|
+
additional attribution notices cannot be construed as modifying
|
|
121
|
+
the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2026 mythicalOS
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/NOTICE
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# @mythicalos/react-ui
|
|
2
|
+
|
|
3
|
+
The mythicalOS **thin React bindings** over `@mythicalos/ui-core` — the executable component
|
|
4
|
+
layer's React half. Apache-2.0.
|
|
5
|
+
|
|
6
|
+
## The thin-binding principle
|
|
7
|
+
|
|
8
|
+
This package is render + framework wiring **only**. Every class string, poll-scheduling policy,
|
|
9
|
+
glyph map, typed-name-match decision, and toast text composition is derived by
|
|
10
|
+
`@mythicalos/ui-core`'s pure, framework-agnostic logic — this package never re-implements any of
|
|
11
|
+
it. A sibling `@mythicalos/preact-ui` binding renders the exact same markup from the exact same core
|
|
12
|
+
functions, so the two frameworks can never drift apart. Genuine React — no `preact/compat`.
|
|
13
|
+
|
|
14
|
+
`react`/`react-dom` are peer dependencies (the consuming app supplies the single instance); so is
|
|
15
|
+
`@mythicalos/tokens` (optional — only needed if you're not already loading it another way).
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm add @mythicalos/react-ui react react-dom
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Import
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { Button, ToastProvider } from "@mythicalos/react-ui";
|
|
27
|
+
|
|
28
|
+
// serve, in order:
|
|
29
|
+
import "@mythicalos/tokens/tokens.css";
|
|
30
|
+
import "@mythicalos/ui-core/styles.css"; // this package ships NO CSS of its own
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Exports
|
|
34
|
+
|
|
35
|
+
- `Button` — 5 variants × 6 states, loading spinner, inert+aria-busy.
|
|
36
|
+
- `Input`, `Toggle`, `Checkbox` — inputs with the "unconfigured is a valid state" neutral empty.
|
|
37
|
+
- `MaskedSecretInput` — presence-only secret slots; the value never round-trips to the UI.
|
|
38
|
+
- `EmptyState` — the designed empty moments (spine motif; never an error tone).
|
|
39
|
+
- `ConfirmDialog` (+ `typedNameMatches`), `Scrim` — modal danger confirms (Esc cancels, safe action
|
|
40
|
+
focused first, optional typed-name-match gate for irreversible deletes).
|
|
41
|
+
- `Toast`, `ToastProvider`, `composeToastText`, `ToastContext`, `useToast` — the toast bus.
|
|
42
|
+
- `usePoll`, `useInterval`, `shouldResetEpoch` — the request/response poll hooks.
|
|
43
|
+
- `Chip`, `Card`, `Avatar`, `StatusLine`, `SearchInput`, `Banner`, `Gauge` — the 7 atoms graduated
|
|
44
|
+
from design-export's proposed additions.
|
|
45
|
+
- Public types: every `*Props` type, the tone/variant enums (`BtnVariant`/`BtnState`, `ChipTone`,
|
|
46
|
+
`StatusTone`, `BannerTone`, `Tone`, `DialogBullet`, `ToastStatus`/`ToastSpec`/`ToastBus`), and the
|
|
47
|
+
hook contracts (`PollResult`, `PollEpochGuard`, `PollTickIO`, `GaugeGeom`).
|
|
48
|
+
- Re-exported core support API — so a consumer can import everything it needs from this one
|
|
49
|
+
package: `buttonClass`, `typedNameMatches`, `BULLET_ICON`, `composeToastText`, `TOAST_SEP`,
|
|
50
|
+
`chipClass`, `statusLineClass`, `bannerClass`, `BANNER_ICON`, `gaugeTone`, `gaugeGeom`,
|
|
51
|
+
`nextPollDelay`, `makePollEpochGuard`, `runPollTick`, `POLL_JITTER_RATIO`, `POLL_BACKOFF_CAP_MS`.
|
|
52
|
+
|
|
53
|
+
## Parity with the Preact sibling
|
|
54
|
+
|
|
55
|
+
`@mythicalos/react-ui` mirrors `@mythicalos/preact-ui`'s export surface name-for-name — same
|
|
56
|
+
components, same hooks, same re-exported core symbols, same `*Props` type names. Two small,
|
|
57
|
+
deliberate API deltas from porting Preact idioms to idiomatic React (documented in full in each
|
|
58
|
+
component's header comment):
|
|
59
|
+
|
|
60
|
+
- `Chip`/`Card`/`Avatar`'s passthrough class prop is `className` here, not `class` — the one
|
|
61
|
+
intentional public-prop rename, because `className` is what every React consumer reaches for.
|
|
62
|
+
- `Input`/`SearchInput` keep the Preact binding's `onInput` prop **name** (so switching bindings
|
|
63
|
+
doesn't rename props at every call site) but wire it internally to React's `onChange` — which
|
|
64
|
+
fires on the same "every keystroke" cadence as the native `input` event Preact's `onInput`
|
|
65
|
+
listens to, so behavior matches, only the DOM event name under the hood differs.
|
|
66
|
+
|
|
67
|
+
Every class string either binding renders is produced by the exact same `@mythicalos/ui-core`
|
|
68
|
+
function call — the two bindings cannot silently drift apart.
|
|
69
|
+
|
|
70
|
+
## Styles
|
|
71
|
+
|
|
72
|
+
This package ships **no stylesheet**. Serve `@mythicalos/ui-core/styles.css` (after
|
|
73
|
+
`@mythicalos/tokens`) — it carries every class this package's components emit, including the 7
|
|
74
|
+
atoms (`.my-chip`, `.my-status`, `.my-card`, `.my-avatar`, `.my-search`, `.my-banner`, `.my-gauge`).
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
Apache-2.0.
|
package/dist/Avatar.d.ts
ADDED
package/dist/Avatar.js
ADDED
package/dist/Banner.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { bannerClass, BANNER_ICON, type BannerTone } from "@mythicalos/ui-core/logic";
|
|
3
|
+
export { bannerClass, BANNER_ICON, type BannerTone };
|
|
4
|
+
export interface BannerProps {
|
|
5
|
+
tone?: BannerTone;
|
|
6
|
+
glyph?: string;
|
|
7
|
+
action?: ReactNode;
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
export declare function Banner(props: BannerProps): import("react").JSX.Element;
|
package/dist/Banner.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { bannerClass, BANNER_ICON } from "@mythicalos/ui-core/logic";
|
|
3
|
+
export { bannerClass, BANNER_ICON };
|
|
4
|
+
export function Banner(props) {
|
|
5
|
+
const { tone = "info", glyph, action, children } = props;
|
|
6
|
+
return (_jsxs("div", { className: bannerClass(tone), children: [_jsx("span", { className: "my-banner__glyph", children: glyph ?? BANNER_ICON[tone] }), _jsx("span", { children: children }), action] }));
|
|
7
|
+
}
|
package/dist/Button.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { MouseEvent, ReactNode } from "react";
|
|
2
|
+
import { buttonClass, type BtnVariant, type BtnState } from "@mythicalos/ui-core/logic";
|
|
3
|
+
export { buttonClass, type BtnVariant, type BtnState };
|
|
4
|
+
export interface ButtonProps {
|
|
5
|
+
variant?: BtnVariant;
|
|
6
|
+
loading?: boolean;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
small?: boolean;
|
|
9
|
+
type?: "button" | "submit";
|
|
10
|
+
title?: string;
|
|
11
|
+
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
}
|
|
14
|
+
export declare function Button(props: ButtonProps): import("react").JSX.Element;
|
package/dist/Button.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { buttonClass } from "@mythicalos/ui-core/logic";
|
|
3
|
+
export { buttonClass };
|
|
4
|
+
export function Button(props) {
|
|
5
|
+
const { variant = "sec", loading = false, disabled = false, small = false } = props;
|
|
6
|
+
const inert = disabled || loading;
|
|
7
|
+
return (_jsxs("button", { type: props.type ?? "button", className: buttonClass(variant, { loading, disabled, small }), disabled: inert, "aria-busy": loading ? "true" : undefined, title: props.title, onClick: inert ? undefined : props.onClick, children: [loading ? _jsx("span", { className: "spin", "aria-hidden": "true" }) : null, props.children] }));
|
|
8
|
+
}
|
package/dist/Card.d.ts
ADDED
package/dist/Card.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
export function Card(props) {
|
|
3
|
+
const { title, flush = false, className: cls = "", children } = props;
|
|
4
|
+
return (_jsxs("section", { className: `my-card${flush ? " my-card--flush" : ""} ${cls}`, children: [title ? _jsx("h3", { className: "my-card__title", children: title }) : null, children] }));
|
|
5
|
+
}
|
package/dist/Chip.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { chipClass, type ChipTone } from "@mythicalos/ui-core/logic";
|
|
3
|
+
export { chipClass, type ChipTone };
|
|
4
|
+
export interface ChipProps {
|
|
5
|
+
tone?: ChipTone;
|
|
6
|
+
className?: string;
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
export declare function Chip(props: ChipProps): import("react").JSX.Element;
|
package/dist/Chip.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { chipClass } from "@mythicalos/ui-core/logic";
|
|
3
|
+
export { chipClass };
|
|
4
|
+
export function Chip(props) {
|
|
5
|
+
const { tone = "neutral", className: cls = "" } = props;
|
|
6
|
+
return _jsx("span", { className: `${chipClass(tone)} ${cls}`, children: props.children });
|
|
7
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { typedNameMatches, BULLET_ICON, type DialogBullet } from "@mythicalos/ui-core/logic";
|
|
3
|
+
export { typedNameMatches, BULLET_ICON, type DialogBullet };
|
|
4
|
+
export declare function Scrim(props: {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
onCancel: () => void;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
8
|
+
export interface ConfirmDialogProps {
|
|
9
|
+
title: string;
|
|
10
|
+
body: ReactNode;
|
|
11
|
+
bullets?: DialogBullet[];
|
|
12
|
+
confirmLabel: string;
|
|
13
|
+
cancelLabel?: string;
|
|
14
|
+
/** Typed-name-match gate (rules-of-use 9): when set, the danger action stays disabled until the
|
|
15
|
+
* operator types EXACTLY this string (e.g. the slot name being deleted). */
|
|
16
|
+
requireName?: string;
|
|
17
|
+
/** Overrides the typed-name entry hint ("Type <name> to confirm" by default). */
|
|
18
|
+
requireNameHint?: string;
|
|
19
|
+
loading?: boolean;
|
|
20
|
+
onConfirm: () => void;
|
|
21
|
+
onCancel: () => void;
|
|
22
|
+
}
|
|
23
|
+
/** A destructive confirm — danger FILL, safe action focused first (Esc cancels), with an optional
|
|
24
|
+
* typed-name-match gate for irreversible deletes. */
|
|
25
|
+
export declare function ConfirmDialog(props: ConfirmDialogProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// @mythicalos/react-ui — confirm dialogs (ds/components-dialogs). Modal 400px, radius 16, modal
|
|
3
|
+
// shadow over the scrim, Esc = cancel, the safe action focused first. Danger FILL is reserved for
|
|
4
|
+
// genuine destructive confirms (book §7.7). The generic, fully prop-driven `ConfirmDialog` (with
|
|
5
|
+
// its optional typed-name-match gate — rules-of-use 9) + the exported `Scrim` are the shared
|
|
6
|
+
// commons; app-specific wrappers compose them in the app (design note §1b-D21: neutral prop
|
|
7
|
+
// names only — no product domain vocabulary leaks from this package).
|
|
8
|
+
//
|
|
9
|
+
// React twin of packages/preact-ui/src/ConfirmDialog.tsx. `typedNameMatches` and
|
|
10
|
+
// `BULLET_ICON`/`DialogBullet` are imported (never re-implemented) from `@mythicalos/ui-core`, same
|
|
11
|
+
// as the Preact sibling. Two Preact→React deltas: (1) the typed-name `<input>` now attaches
|
|
12
|
+
// `onChange` unconditionally (it always has local state behind it, so this is really a no-op
|
|
13
|
+
// change); (2) the bullets `<li>` list now carries an explicit `key` — React's dev-mode key
|
|
14
|
+
// warning fires at element-creation time (even under `renderToStaticMarkup`), while Preact never
|
|
15
|
+
// checks for one, so a bare `.map()` here would print console noise the Preact sibling never has.
|
|
16
|
+
// `window`'s native KeyboardEvent below is the DOM global, not React's synthetic type — no `react`
|
|
17
|
+
// import shadows it in this file.
|
|
18
|
+
import { useEffect, useRef, useState } from "react";
|
|
19
|
+
import { typedNameMatches, BULLET_ICON } from "@mythicalos/ui-core/logic";
|
|
20
|
+
export { typedNameMatches, BULLET_ICON };
|
|
21
|
+
export function Scrim(props) {
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const onKey = (e) => {
|
|
24
|
+
if (e.key === "Escape")
|
|
25
|
+
props.onCancel();
|
|
26
|
+
};
|
|
27
|
+
window.addEventListener("keydown", onKey);
|
|
28
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
29
|
+
}, [props.onCancel]);
|
|
30
|
+
return (_jsx("div", { className: "scrim", role: "dialog", "aria-modal": "true", children: _jsx("div", { className: "dlg", children: props.children }) }));
|
|
31
|
+
}
|
|
32
|
+
/** A destructive confirm — danger FILL, safe action focused first (Esc cancels), with an optional
|
|
33
|
+
* typed-name-match gate for irreversible deletes. */
|
|
34
|
+
export function ConfirmDialog(props) {
|
|
35
|
+
const cancelRef = useRef(null);
|
|
36
|
+
const [typed, setTyped] = useState("");
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
cancelRef.current?.focus();
|
|
39
|
+
}, []);
|
|
40
|
+
const nameOk = typedNameMatches(props.requireName, typed);
|
|
41
|
+
const confirmDisabled = props.loading === true || !nameOk;
|
|
42
|
+
return (_jsxs(Scrim, { onCancel: props.onCancel, children: [_jsx("h2", { children: props.title }), _jsx("p", { children: props.body }), props.bullets ? (_jsx("ul", { children: props.bullets.map((b, i) => (_jsxs("li", { children: [_jsx("i", { className: `ic-${b.tone}`, "aria-hidden": "true", children: BULLET_ICON[b.tone] }), _jsx("span", { children: b.text })] }, i))) })) : null, props.requireName !== undefined ? (_jsxs("label", { className: "field", children: [_jsx("span", { className: "field-label", children: props.requireNameHint ?? `Type ${props.requireName} to confirm` }), _jsx("input", { className: "input mono", type: "text", value: typed, placeholder: props.requireName, autoComplete: "off", autoCapitalize: "off", spellCheck: false, onChange: (e) => setTyped(e.target.value) })] })) : null, _jsxs("div", { className: "dlg-foot", children: [_jsx("button", { ref: cancelRef, type: "button", className: "btn btn--sec", onClick: props.onCancel, children: props.cancelLabel ?? "Cancel" }), _jsxs("button", { type: "button", className: `btn danf${confirmDisabled ? " is-disabled" : ""}`, disabled: confirmDisabled, "aria-busy": props.loading ? "true" : undefined, onClick: () => {
|
|
43
|
+
if (!confirmDisabled)
|
|
44
|
+
props.onConfirm();
|
|
45
|
+
}, children: [props.loading ? _jsx("span", { className: "spin", "aria-hidden": "true" }) : null, props.confirmLabel] })] }), _jsx("div", { className: "esc", children: "Esc cancels \u00B7 focus starts on the safe action" })] }));
|
|
46
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export interface EmptyStateProps {
|
|
3
|
+
variant: "unconfigured" | "asleep" | "neutral";
|
|
4
|
+
title: string;
|
|
5
|
+
body: string;
|
|
6
|
+
actions?: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
export declare function EmptyState(props: EmptyStateProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
function SpineArt(props) {
|
|
3
|
+
if (props.variant === "unconfigured") {
|
|
4
|
+
// solid up to the live petrol tip, dashed beyond; hollow muted nodes = not-yet
|
|
5
|
+
// (§1a-8: package-owned tokenized classes — no literal strokes, no gauge-block classes)
|
|
6
|
+
return (_jsxs("svg", { className: "spine-empty", viewBox: "0 0 200 36", role: "img", "aria-label": "setup is the live step", children: [_jsx("line", { className: "spine-track", x1: "10", y1: "18", x2: "118", y2: "18", strokeWidth: "3" }), _jsx("line", { className: "spine-dash", x1: "118", y1: "18", x2: "190", y2: "18", strokeWidth: "3", strokeDasharray: "2 7", strokeLinecap: "round" }), _jsx("circle", { className: "spine-node", cx: "10", cy: "18", r: "7", strokeWidth: "3" }), _jsx("circle", { className: "spine-node", cx: "64", cy: "18", r: "7", strokeWidth: "3" }), _jsx("circle", { className: "spine-here", cx: "118", cy: "18", r: "8" })] }));
|
|
7
|
+
}
|
|
8
|
+
if (props.variant === "asleep") {
|
|
9
|
+
return (_jsxs("svg", { className: "spine-empty", viewBox: "0 0 200 36", role: "img", "aria-label": "the hollow tip waits", children: [_jsx("line", { className: "spine-track", x1: "10", y1: "18", x2: "190", y2: "18", strokeWidth: "3" }), _jsx("circle", { className: "spine-dot", cx: "10", cy: "18", r: "7" }), _jsx("circle", { className: "spine-dot", cx: "64", cy: "18", r: "7" }), _jsx("circle", { className: "spine-dot", cx: "118", cy: "18", r: "7" }), _jsx("circle", { className: "spine-node", cx: "182", cy: "18", r: "8", strokeWidth: "3" })] }));
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
export function EmptyState(props) {
|
|
14
|
+
return (_jsxs("div", { className: "empty", children: [_jsx(SpineArt, { variant: props.variant }), _jsx("h2", { children: props.title }), _jsx("p", { children: props.body }), props.actions ? _jsx("div", { className: "ebtns", children: props.actions }) : null] }));
|
|
15
|
+
}
|
package/dist/Gauge.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { gaugeGeom, gaugeTone, type GaugeGeom, type Tone } from "@mythicalos/ui-core/logic";
|
|
2
|
+
export { gaugeGeom, gaugeTone, type GaugeGeom, type Tone };
|
|
3
|
+
export interface GaugeProps {
|
|
4
|
+
pct?: number;
|
|
5
|
+
size?: number;
|
|
6
|
+
stroke?: number;
|
|
7
|
+
showLabel?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function Gauge(props: GaugeProps): import("react").JSX.Element;
|
package/dist/Gauge.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// @mythicalos/react-ui — circular gauge for context/usage. `pct` 0–100. Fill color steps by
|
|
3
|
+
// threshold per token rule #4: accent < 75%, warn 75–89%, error ≥ 90%. Pure SVG presentation
|
|
4
|
+
// attributes (no inline CSS) — the stroke color is a class, the arc is a dash attribute.
|
|
5
|
+
//
|
|
6
|
+
// React twin of packages/preact-ui/src/Gauge.tsx. `gaugeGeom`/`gaugeTone` (geometry + threshold
|
|
7
|
+
// math) are imported from `@mythicalos/ui-core`, never reimplemented — this binding only renders
|
|
8
|
+
// the SVG from the values those functions return. SVG stroke attributes go through React's
|
|
9
|
+
// camelCase props (`strokeWidth`/`strokeDasharray`/`strokeDashoffset`), which serialize back to the
|
|
10
|
+
// same kebab-case DOM attributes as the Preact sibling's output.
|
|
11
|
+
import { gaugeGeom, gaugeTone } from "@mythicalos/ui-core/logic";
|
|
12
|
+
export { gaugeGeom, gaugeTone };
|
|
13
|
+
export function Gauge(props) {
|
|
14
|
+
const { pct = 0, size = 44, stroke = 4, showLabel = true } = props;
|
|
15
|
+
const { r, circ, offset } = gaugeGeom(size, stroke, pct);
|
|
16
|
+
const tone = gaugeTone(pct);
|
|
17
|
+
// gaugeGeom/gaugeTone each clamp `pct` to 0–100 independently (ui-core's gauge.ts: "so either
|
|
18
|
+
// can be called on its own ... and stay consistent with the other"); the label needs that same
|
|
19
|
+
// 0–100 clamp to restate the identical number as a percentage — trivial arithmetic, not the
|
|
20
|
+
// tone/geometry derivation itself, which stays entirely in gaugeTone/gaugeGeom above.
|
|
21
|
+
const clampedPct = Math.round(Math.max(0, Math.min(100, pct)));
|
|
22
|
+
return (_jsxs("span", { className: "my-gauge__wrap", children: [_jsx("span", { className: "my-gauge", children: _jsxs("svg", { width: size, height: size, children: [_jsx("circle", { className: "my-gauge__track", cx: size / 2, cy: size / 2, r: r, strokeWidth: stroke }), _jsx("circle", { className: `my-gauge__fill my-gauge__fill--${tone}`, cx: size / 2, cy: size / 2, r: r, strokeWidth: stroke, strokeDasharray: circ, strokeDashoffset: offset })] }) }), showLabel ? _jsxs("span", { className: "my-gauge__label", children: [clampedPct, "%"] }) : null] }));
|
|
23
|
+
}
|
package/dist/Input.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ReactNode, KeyboardEvent } from "react";
|
|
2
|
+
export interface InputProps {
|
|
3
|
+
label?: ReactNode;
|
|
4
|
+
value?: string;
|
|
5
|
+
placeholder?: string;
|
|
6
|
+
error?: string;
|
|
7
|
+
help?: ReactNode;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
readOnly?: boolean;
|
|
10
|
+
mono?: boolean;
|
|
11
|
+
dirty?: boolean;
|
|
12
|
+
type?: string;
|
|
13
|
+
id?: string;
|
|
14
|
+
onInput?: (value: string) => void;
|
|
15
|
+
onKeyDown?: (e: KeyboardEvent<HTMLInputElement>) => void;
|
|
16
|
+
}
|
|
17
|
+
export declare function Input(props: InputProps): import("react").JSX.Element;
|
|
18
|
+
export interface ToggleProps {
|
|
19
|
+
on: boolean;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
label?: string;
|
|
22
|
+
onToggle?: (next: boolean) => void;
|
|
23
|
+
}
|
|
24
|
+
export declare function Toggle(props: ToggleProps): import("react").JSX.Element;
|
|
25
|
+
export interface CheckboxProps {
|
|
26
|
+
checked: boolean;
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
label?: ReactNode;
|
|
29
|
+
onToggle?: (next: boolean) => void;
|
|
30
|
+
}
|
|
31
|
+
export declare function Checkbox(props: CheckboxProps): import("react").JSX.Element;
|
package/dist/Input.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
export function Input(props) {
|
|
3
|
+
const cls = ["input"];
|
|
4
|
+
if (props.error)
|
|
5
|
+
cls.push("is-err");
|
|
6
|
+
if (props.mono)
|
|
7
|
+
cls.push("mono");
|
|
8
|
+
if (props.dirty)
|
|
9
|
+
cls.push("is-dirty");
|
|
10
|
+
if (props.readOnly)
|
|
11
|
+
cls.push("readonly-input");
|
|
12
|
+
const field = (_jsxs(_Fragment, { children: [_jsx("input", { id: props.id, type: props.type ?? "text", className: cls.join(" "), value: props.value ?? "", placeholder: props.placeholder, disabled: props.disabled, readOnly: props.readOnly,
|
|
13
|
+
// Local single-user config UI: these are identifiers/paths/config values (slugs, names,
|
|
14
|
+
// model ids, urls), never autofill targets — kill browser autocomplete, the annoying
|
|
15
|
+
// first-letter auto-capitalize, autocorrect, and spellcheck squiggles on all of them.
|
|
16
|
+
autoComplete: "off", autoCapitalize: "off", autoCorrect: "off", spellCheck: false, "aria-invalid": props.error ? "true" : undefined, onChange: (e) => props.onInput?.(e.target.value), onKeyDown: props.onKeyDown }), props.help && !props.error ? _jsx("div", { className: "help", children: props.help }) : null, props.error ? (_jsxs("div", { className: "emsg", children: [_jsx("span", { "aria-hidden": "true", children: "\u26A0" }), _jsx("span", { children: props.error })] })) : null] }));
|
|
17
|
+
if (!props.label)
|
|
18
|
+
return field;
|
|
19
|
+
return (_jsxs("label", { className: "field", children: [_jsx("span", { className: "field-label", children: props.label }), field] }));
|
|
20
|
+
}
|
|
21
|
+
export function Toggle(props) {
|
|
22
|
+
const cls = ["tog"];
|
|
23
|
+
if (!props.on)
|
|
24
|
+
cls.push("is-off");
|
|
25
|
+
if (props.disabled)
|
|
26
|
+
cls.push("is-disabled");
|
|
27
|
+
return (_jsx("button", { type: "button", className: cls.join(" "), role: "switch", "aria-checked": props.on ? "true" : "false", "aria-label": props.label, disabled: props.disabled, onClick: props.disabled ? undefined : () => props.onToggle?.(!props.on), children: _jsx("i", { "aria-hidden": "true" }) }));
|
|
28
|
+
}
|
|
29
|
+
export function Checkbox(props) {
|
|
30
|
+
const cls = ["cb"];
|
|
31
|
+
if (props.checked)
|
|
32
|
+
cls.push("is-on");
|
|
33
|
+
if (props.disabled)
|
|
34
|
+
cls.push("is-disabled");
|
|
35
|
+
const box = (_jsx("button", { type: "button", className: cls.join(" "), role: "checkbox", "aria-checked": props.checked ? "true" : "false", disabled: props.disabled, onClick: props.disabled ? undefined : () => props.onToggle?.(!props.checked), children: props.checked ? _jsx("span", { "aria-hidden": "true", children: "\u2713" }) : null }));
|
|
36
|
+
if (!props.label)
|
|
37
|
+
return box;
|
|
38
|
+
return (_jsxs("label", { className: "check-row", children: [box, _jsx("span", { children: props.label })] }));
|
|
39
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface MaskedSecretInputProps {
|
|
2
|
+
label: string;
|
|
3
|
+
/** Full slot name, e.g. "model/claude" — shown as the chip. */
|
|
4
|
+
slot: string;
|
|
5
|
+
/** True when the server's secrets list contains `slot`. */
|
|
6
|
+
filled: boolean;
|
|
7
|
+
/** The server's masked echo, when known (e.g. "sk-…3kfa"). */
|
|
8
|
+
masked?: string;
|
|
9
|
+
/** Labels the entry lane so the operator knows what to paste. */
|
|
10
|
+
entryHint?: string;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
/** Called with the raw entered value on submit — the parent PUTs it and never reads it back. */
|
|
13
|
+
onSubmit?: (value: string) => void;
|
|
14
|
+
}
|
|
15
|
+
export declare function MaskedSecretInput(props: MaskedSecretInputProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// @mythicalos/react-ui — masked secret input with slot chip (ds/components-inputs "secret slots").
|
|
3
|
+
// A filled slot shows the server's mask (readonly — the value never round-trips to the UI) + the
|
|
4
|
+
// slot chip + a "Replace" affordance that re-opens entry; an unset slot shows the dashed empty
|
|
5
|
+
// chip ("unconfigured is a valid state"). Submitting a new value is the PARENT's concern (it PUTs
|
|
6
|
+
// to the secrets route and never reads the value back); this component is presentation + entry only.
|
|
7
|
+
//
|
|
8
|
+
// React twin of packages/preact-ui/src/MaskedSecretInput.tsx. Both the filled-mask input (readOnly,
|
|
9
|
+
// no handler needed — React's controlled-without-onChange warning is suppressed by `readOnly`) and
|
|
10
|
+
// the entry input (local `useState`, always has a real `onChange`) are already React-safe with no
|
|
11
|
+
// extra wiring required.
|
|
12
|
+
import { useState } from "react";
|
|
13
|
+
import { Button } from "./Button.js";
|
|
14
|
+
export function MaskedSecretInput(props) {
|
|
15
|
+
const [editing, setEditing] = useState(!props.filled);
|
|
16
|
+
const [value, setValue] = useState("");
|
|
17
|
+
const chip = props.filled ? (_jsx("span", { className: "chip", children: props.slot })) : (_jsx("span", { className: "chip chip--empty", children: "slot empty" }));
|
|
18
|
+
if (props.filled && !editing) {
|
|
19
|
+
return (_jsxs("label", { className: "field", children: [_jsx("span", { className: "field-label", children: props.label }), _jsxs("div", { className: "slot", children: [_jsx("input", { className: "input mono readonly-input", value: props.masked ?? "••••••••", readOnly: true }), chip, _jsx("button", { type: "button", className: "rep", disabled: props.disabled, onClick: () => setEditing(true), children: "Replace" })] }), _jsx("div", { className: "help", children: "Readonly mask; Replace re-opens entry. The value never round-trips to the UI." })] }));
|
|
20
|
+
}
|
|
21
|
+
return (_jsxs("label", { className: "field", children: [_jsx("span", { className: "field-label", children: props.label }), _jsxs("div", { className: "slot", children: [_jsx("input", { className: "input mono",
|
|
22
|
+
// diff r1-F10 (S1a-11): a replacement credential is a SECRET while typed — masked entry
|
|
23
|
+
// (type=password) + new-password so the browser neither displays nor offers to save it.
|
|
24
|
+
type: "password", value: value, placeholder: props.entryHint ?? "paste token…", disabled: props.disabled,
|
|
25
|
+
// A pasted credential: never autofill/autocapitalize/autocorrect/spellcheck it.
|
|
26
|
+
autoComplete: "new-password", autoCapitalize: "off", autoCorrect: "off", spellCheck: false, onChange: (e) => setValue(e.target.value) }), chip, _jsx(Button, { variant: "sec", small: true, disabled: props.disabled || value.trim().length === 0, onClick: () => {
|
|
27
|
+
props.onSubmit?.(value);
|
|
28
|
+
setValue("");
|
|
29
|
+
if (props.filled)
|
|
30
|
+
setEditing(false);
|
|
31
|
+
}, children: "Save" })] }), _jsx("div", { className: "help", children: "Unconfigured is a valid state \u2014 no error until submit." })] }));
|
|
32
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ChangeEvent, MouseEvent } from "react";
|
|
2
|
+
export interface SearchInputProps {
|
|
3
|
+
value?: string;
|
|
4
|
+
placeholder?: string;
|
|
5
|
+
onInput?: (e: ChangeEvent<HTMLInputElement>) => void;
|
|
6
|
+
onClear?: (e: MouseEvent<HTMLButtonElement>) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function SearchInput(props: SearchInputProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
const noop = () => { };
|
|
3
|
+
export function SearchInput(props) {
|
|
4
|
+
const { value, onInput, onClear, placeholder = "Search…" } = props;
|
|
5
|
+
return (_jsxs("div", { className: "my-search", children: [_jsx("span", { className: "my-search__icon", children: "\u2315" }), _jsx("input", { className: "my-search__input", value: value, onChange: onInput ?? noop, placeholder: placeholder }), value ? (_jsx("button", { type: "button", className: "my-search__clear", title: "Clear", onClick: onClear, children: "\u2715" })) : null] }));
|
|
6
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { statusLineClass, type StatusTone } from "@mythicalos/ui-core/logic";
|
|
3
|
+
export { statusLineClass, type StatusTone };
|
|
4
|
+
export interface StatusLineProps {
|
|
5
|
+
tone?: StatusTone;
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
export declare function StatusLine(props: StatusLineProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { statusLineClass } from "@mythicalos/ui-core/logic";
|
|
3
|
+
export { statusLineClass };
|
|
4
|
+
export function StatusLine(props) {
|
|
5
|
+
const { tone = "ok" } = props;
|
|
6
|
+
return (_jsxs("span", { className: statusLineClass(tone), children: [_jsx("span", { className: "my-status__dot" }), props.children] }));
|
|
7
|
+
}
|
package/dist/Toast.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { type ToastStatus } from "./toast-context.js";
|
|
3
|
+
import { composeToastText, TOAST_SEP } from "@mythicalos/ui-core/logic";
|
|
4
|
+
export { composeToastText, TOAST_SEP };
|
|
5
|
+
export declare function ToastProvider(props: {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
8
|
+
export interface ToastProps {
|
|
9
|
+
status: ToastStatus;
|
|
10
|
+
title: string;
|
|
11
|
+
body?: string;
|
|
12
|
+
action?: {
|
|
13
|
+
label: string;
|
|
14
|
+
onAction: () => void;
|
|
15
|
+
};
|
|
16
|
+
onClose?: () => void;
|
|
17
|
+
}
|
|
18
|
+
export declare function Toast(props: ToastProps): import("react").JSX.Element;
|
package/dist/Toast.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
// @mythicalos/react-ui — toasts (ds/components-toasts): tone-colored (ok = surface/ink/border;
|
|
3
|
+
// warn/error/info = soft fill + status ink/border), icon + bold lead carry the state (never color
|
|
4
|
+
// alone). Auto-dismiss every tone on one cadence; each stacked toast runs its own timer. Max 3
|
|
5
|
+
// visible — older collapse into "+n more".
|
|
6
|
+
//
|
|
7
|
+
// React twin of packages/preact-ui/src/Toast.tsx. `composeToastText`/`TOAST_SEP` are imported
|
|
8
|
+
// (never re-implemented) from `@mythicalos/ui-core`; the tone→icon/dismiss-cadence lookup tables
|
|
9
|
+
// stay local, same as the Preact sibling (literal presentational maps, not branchy derivation).
|
|
10
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
11
|
+
import { ToastContext } from "./toast-context.js";
|
|
12
|
+
import { composeToastText, TOAST_SEP } from "@mythicalos/ui-core/logic";
|
|
13
|
+
export { composeToastText, TOAST_SEP };
|
|
14
|
+
const ICON = { ok: "✓", info: "ℹ", warn: "▲", error: "⬤" };
|
|
15
|
+
const DISMISS_MS = { ok: 2600, info: 2600, warn: 2600, error: 2600 };
|
|
16
|
+
export function ToastProvider(props) {
|
|
17
|
+
const [toasts, setToasts] = useState([]);
|
|
18
|
+
const nextId = useRef(1);
|
|
19
|
+
const timers = useRef(new Map());
|
|
20
|
+
const dismiss = useCallback((id) => {
|
|
21
|
+
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
22
|
+
const timer = timers.current.get(id);
|
|
23
|
+
if (timer) {
|
|
24
|
+
clearTimeout(timer);
|
|
25
|
+
timers.current.delete(id);
|
|
26
|
+
}
|
|
27
|
+
}, []);
|
|
28
|
+
const push = useCallback((spec) => {
|
|
29
|
+
const id = nextId.current++;
|
|
30
|
+
setToasts((prev) => [...prev, { ...spec, id }]);
|
|
31
|
+
const ms = DISMISS_MS[spec.status];
|
|
32
|
+
if (ms !== null) {
|
|
33
|
+
timers.current.set(id, setTimeout(() => dismiss(id), ms));
|
|
34
|
+
}
|
|
35
|
+
}, [dismiss]);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const map = timers.current;
|
|
38
|
+
return () => {
|
|
39
|
+
for (const t of map.values())
|
|
40
|
+
clearTimeout(t);
|
|
41
|
+
map.clear();
|
|
42
|
+
};
|
|
43
|
+
}, []);
|
|
44
|
+
const bus = useMemo(() => ({ push }), [push]);
|
|
45
|
+
const visible = toasts.slice(-3);
|
|
46
|
+
const hiddenCount = toasts.length - visible.length;
|
|
47
|
+
return (_jsxs(ToastContext.Provider, { value: bus, children: [props.children, _jsxs("div", { className: "toast-stack", "aria-live": "polite", children: [hiddenCount > 0 ? _jsxs("div", { className: "toast-more", children: ["+", hiddenCount, " more"] }) : null, visible.map((t) => (_jsx(Toast, { status: t.status, title: t.title, body: t.body, action: t.action, onClose: () => dismiss(t.id) }, t.id)))] })] }));
|
|
48
|
+
}
|
|
49
|
+
export function Toast(props) {
|
|
50
|
+
return (_jsxs("div", { className: `toast toast--${props.status}`, role: "status", children: [_jsx("i", { className: "toast-icon", "aria-hidden": "true", children: ICON[props.status] }), _jsxs("span", { children: [_jsx("b", { children: props.title }), props.body ? (_jsxs(_Fragment, { children: [TOAST_SEP, props.body] })) : null] }), props.action ? (_jsx("button", { type: "button", className: "toast-action", onClick: props.action.onAction, children: props.action.label })) : null, _jsx("button", { type: "button", className: "toast-x", "aria-label": "Dismiss", onClick: props.onClose, children: "\u2715" })] }));
|
|
51
|
+
}
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { nextPollDelay, makePollEpochGuard, runPollTick, shouldResetEpoch, POLL_JITTER_RATIO, POLL_BACKOFF_CAP_MS, type PollResult, type PollEpochGuard, type PollTickIO } from "@mythicalos/ui-core/logic";
|
|
2
|
+
export { nextPollDelay, makePollEpochGuard, runPollTick, shouldResetEpoch, POLL_JITTER_RATIO, POLL_BACKOFF_CAP_MS, type PollResult, type PollEpochGuard, type PollTickIO, };
|
|
3
|
+
/**
|
|
4
|
+
* Poll `fn` on the `ms` cadence while `enabled`, under the U7 policy: every delay ±20% jittered,
|
|
5
|
+
* exponential ×2 backoff on consecutive failures capped at 60 s (reset on success), and
|
|
6
|
+
* `document.hidden` PAUSES the loop (immediate refresh when visibility returns). Fires immediately
|
|
7
|
+
* on mount / when a dep changes, cancels in-flight results after unmount, and never overlaps a
|
|
8
|
+
* slow response with the next tick — ONE scheduler per call site, never parallel duplicate polls.
|
|
9
|
+
* `fn` should resolve (never reject) with the value; thrown errors surface in `error`.
|
|
10
|
+
*
|
|
11
|
+
* Pass an `epochKey` to opt into epoch isolation: a change of key (or an `enabled` false→true
|
|
12
|
+
* transition) clears the retained `data`/`error` and returns to `loading` until the new epoch's
|
|
13
|
+
* first settle. Without `epochKey` the hook retains data/error across toggles and dep changes.
|
|
14
|
+
*/
|
|
15
|
+
export declare function usePoll<T>(fn: () => Promise<T>, ms: number, opts?: {
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
deps?: readonly unknown[];
|
|
18
|
+
epochKey?: unknown;
|
|
19
|
+
}): PollResult<T>;
|
|
20
|
+
/** Run `fn` on a fixed interval while `enabled` (no return value; for fire-and-forget effects). */
|
|
21
|
+
export declare function useInterval(fn: () => void, ms: number, enabled?: boolean): void;
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// @mythicalos/react-ui — small shared hooks (polling + interval). A family UI talks plain
|
|
2
|
+
// request/response to its service; pages poll on their cadence via `usePoll`. Extends the U7
|
|
3
|
+
// polling policy (design note §1b-D7, r2-F8): per-call interval, ±20% jitter on every delay,
|
|
4
|
+
// exponential ×2 backoff on consecutive failures capped at 60 s (reset on success), and
|
|
5
|
+
// `document.hidden` pauses the loop with an immediate refresh on visibility return.
|
|
6
|
+
//
|
|
7
|
+
// React twin of packages/preact-ui/src/hooks.ts. The pure, DOM-free scheduling math
|
|
8
|
+
// (`nextPollDelay`, `makePollEpochGuard`, `runPollTick`, `shouldResetEpoch` — the r3-F1 fire-time
|
|
9
|
+
// epoch stamp) lives in `@mythicalos/ui-core` and is imported, never reimplemented — identical to
|
|
10
|
+
// the Preact sibling. Only the actual React hooks (`usePoll`/`useInterval`, which import `react`
|
|
11
|
+
// instead of `preact/hooks`) differ; the hook bodies below are otherwise verbatim.
|
|
12
|
+
import { useEffect, useRef, useState } from "react";
|
|
13
|
+
import { nextPollDelay, makePollEpochGuard, runPollTick, shouldResetEpoch, POLL_JITTER_RATIO, POLL_BACKOFF_CAP_MS, } from "@mythicalos/ui-core/logic";
|
|
14
|
+
export { nextPollDelay, makePollEpochGuard, runPollTick, shouldResetEpoch, POLL_JITTER_RATIO, POLL_BACKOFF_CAP_MS, };
|
|
15
|
+
/**
|
|
16
|
+
* Poll `fn` on the `ms` cadence while `enabled`, under the U7 policy: every delay ±20% jittered,
|
|
17
|
+
* exponential ×2 backoff on consecutive failures capped at 60 s (reset on success), and
|
|
18
|
+
* `document.hidden` PAUSES the loop (immediate refresh when visibility returns). Fires immediately
|
|
19
|
+
* on mount / when a dep changes, cancels in-flight results after unmount, and never overlaps a
|
|
20
|
+
* slow response with the next tick — ONE scheduler per call site, never parallel duplicate polls.
|
|
21
|
+
* `fn` should resolve (never reject) with the value; thrown errors surface in `error`.
|
|
22
|
+
*
|
|
23
|
+
* Pass an `epochKey` to opt into epoch isolation: a change of key (or an `enabled` false→true
|
|
24
|
+
* transition) clears the retained `data`/`error` and returns to `loading` until the new epoch's
|
|
25
|
+
* first settle. Without `epochKey` the hook retains data/error across toggles and dep changes.
|
|
26
|
+
*/
|
|
27
|
+
export function usePoll(fn, ms, opts = {}) {
|
|
28
|
+
const enabled = opts.enabled ?? true;
|
|
29
|
+
const deps = opts.deps ?? [];
|
|
30
|
+
const useEpoch = "epochKey" in opts;
|
|
31
|
+
const epochKey = opts.epochKey;
|
|
32
|
+
const [data, setData] = useState(undefined);
|
|
33
|
+
const [error, setError] = useState(undefined);
|
|
34
|
+
const [loading, setLoading] = useState(enabled);
|
|
35
|
+
const [nonce, setNonce] = useState(0);
|
|
36
|
+
const fnRef = useRef(fn);
|
|
37
|
+
fnRef.current = fn;
|
|
38
|
+
// diff r3-F1: one persistent guard per call site — the render-time reset below invalidates it
|
|
39
|
+
// IMMEDIATELY, so an in-flight response for the old epoch is dropped even though the old
|
|
40
|
+
// effect's cleanup (which flips `alive`) only runs after paint.
|
|
41
|
+
const guardRef = useRef(null);
|
|
42
|
+
if (guardRef.current === null)
|
|
43
|
+
guardRef.current = makePollEpochGuard();
|
|
44
|
+
const guard = guardRef.current;
|
|
45
|
+
const epochRef = useRef({ key: epochKey, enabled });
|
|
46
|
+
if (shouldResetEpoch(useEpoch, epochRef.current, epochKey, enabled)) {
|
|
47
|
+
guard.invalidate(); // FIRST — stale in-flight responses die here, not at deferred cleanup (r3-F1)
|
|
48
|
+
if (data !== undefined)
|
|
49
|
+
setData(undefined);
|
|
50
|
+
if (error !== undefined)
|
|
51
|
+
setError(undefined);
|
|
52
|
+
if (enabled && !loading)
|
|
53
|
+
setLoading(true);
|
|
54
|
+
}
|
|
55
|
+
if (useEpoch)
|
|
56
|
+
epochRef.current = { key: epochKey, enabled };
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!enabled) {
|
|
59
|
+
setLoading(false);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
let alive = true;
|
|
63
|
+
let timer;
|
|
64
|
+
let stopped = false;
|
|
65
|
+
let failures = 0; // consecutive failed reads — drives the ×2 backoff ladder (reset on success)
|
|
66
|
+
const isHidden = () => typeof document !== "undefined" && document.hidden === true;
|
|
67
|
+
const schedule = () => {
|
|
68
|
+
if (!alive || stopped)
|
|
69
|
+
return;
|
|
70
|
+
// U7 pause: while the document is hidden no timer is armed — the
|
|
71
|
+
// visibilitychange handler below refreshes immediately on return.
|
|
72
|
+
if (isHidden())
|
|
73
|
+
return;
|
|
74
|
+
timer = setTimeout(() => void tick(), nextPollDelay(ms, failures));
|
|
75
|
+
};
|
|
76
|
+
// diff r3-F1: every response routes through runPollTick — stamped with the epoch at fire
|
|
77
|
+
// time, applied only while that epoch is still live. A stale settle leaves loading alone
|
|
78
|
+
// (the reset already set it for the new epoch) but still rearms via schedule()'s own guards.
|
|
79
|
+
const tick = () => runPollTick({
|
|
80
|
+
fetch: () => fnRef.current(),
|
|
81
|
+
guard,
|
|
82
|
+
isAlive: () => alive && !stopped,
|
|
83
|
+
isHidden,
|
|
84
|
+
onSuccess: (v) => {
|
|
85
|
+
failures = 0;
|
|
86
|
+
setData(v);
|
|
87
|
+
setError(undefined);
|
|
88
|
+
},
|
|
89
|
+
onFailure: (message) => {
|
|
90
|
+
failures += 1;
|
|
91
|
+
setError(message);
|
|
92
|
+
},
|
|
93
|
+
onSettled: (applied) => {
|
|
94
|
+
if (applied)
|
|
95
|
+
setLoading(false);
|
|
96
|
+
schedule();
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
const onVisibility = () => {
|
|
100
|
+
if (!alive || stopped)
|
|
101
|
+
return;
|
|
102
|
+
if (timer) {
|
|
103
|
+
clearTimeout(timer);
|
|
104
|
+
timer = undefined;
|
|
105
|
+
}
|
|
106
|
+
if (!isHidden())
|
|
107
|
+
void tick(); // immediate refresh on visibility return
|
|
108
|
+
};
|
|
109
|
+
if (typeof document !== "undefined")
|
|
110
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
111
|
+
setLoading(true);
|
|
112
|
+
void tick();
|
|
113
|
+
return () => {
|
|
114
|
+
alive = false;
|
|
115
|
+
stopped = true;
|
|
116
|
+
guard.invalidate(); // r3-F1: teardown supersedes the epoch for legacy (no-epochKey) callers too
|
|
117
|
+
if (timer)
|
|
118
|
+
clearTimeout(timer);
|
|
119
|
+
if (typeof document !== "undefined")
|
|
120
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
121
|
+
};
|
|
122
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
123
|
+
}, [enabled, ms, nonce, epochKey, ...deps]);
|
|
124
|
+
return { data, error, loading, refresh: () => setNonce((n) => n + 1) };
|
|
125
|
+
}
|
|
126
|
+
/** Run `fn` on a fixed interval while `enabled` (no return value; for fire-and-forget effects). */
|
|
127
|
+
export function useInterval(fn, ms, enabled = true) {
|
|
128
|
+
const fnRef = useRef(fn);
|
|
129
|
+
fnRef.current = fn;
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (!enabled)
|
|
132
|
+
return;
|
|
133
|
+
const id = setInterval(() => fnRef.current(), ms);
|
|
134
|
+
return () => clearInterval(id);
|
|
135
|
+
}, [ms, enabled]);
|
|
136
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { Button, buttonClass, type ButtonProps, type BtnVariant, type BtnState } from "./Button.js";
|
|
2
|
+
export { Input, Toggle, Checkbox, type InputProps, type ToggleProps, type CheckboxProps } from "./Input.js";
|
|
3
|
+
export { MaskedSecretInput, type MaskedSecretInputProps } from "./MaskedSecretInput.js";
|
|
4
|
+
export { EmptyState, type EmptyStateProps } from "./EmptyState.js";
|
|
5
|
+
export { Scrim, ConfirmDialog, typedNameMatches, BULLET_ICON, type DialogBullet, type ConfirmDialogProps, } from "./ConfirmDialog.js";
|
|
6
|
+
export { Toast, ToastProvider, composeToastText, TOAST_SEP, type ToastProps, } from "./Toast.js";
|
|
7
|
+
export { ToastContext, useToast, type ToastStatus, type ToastSpec, type ToastBus, } from "./toast-context.js";
|
|
8
|
+
export { usePoll, useInterval, shouldResetEpoch, nextPollDelay, makePollEpochGuard, runPollTick, POLL_JITTER_RATIO, POLL_BACKOFF_CAP_MS, type PollResult, type PollEpochGuard, type PollTickIO, } from "./hooks.js";
|
|
9
|
+
export { Chip, chipClass, type ChipProps, type ChipTone } from "./Chip.js";
|
|
10
|
+
export { Card, type CardProps } from "./Card.js";
|
|
11
|
+
export { Avatar, type AvatarProps } from "./Avatar.js";
|
|
12
|
+
export { StatusLine, statusLineClass, type StatusLineProps, type StatusTone } from "./StatusLine.js";
|
|
13
|
+
export { SearchInput, type SearchInputProps } from "./SearchInput.js";
|
|
14
|
+
export { Banner, bannerClass, BANNER_ICON, type BannerProps, type BannerTone } from "./Banner.js";
|
|
15
|
+
export { Gauge, gaugeTone, gaugeGeom, type GaugeProps, type GaugeGeom, type Tone } from "./Gauge.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// @mythicalos/react-ui — barrel. React twin of packages/preact-ui/src/index.ts — the SAME export
|
|
2
|
+
// surface (components, hooks, re-exported core symbols, Props types) so the two bindings can never
|
|
3
|
+
// drift. The two intentional prop-name deltas (Chip/Card/Avatar's `class` → `className`) live on
|
|
4
|
+
// the component types themselves, not the barrel — see each file's header comment and the task
|
|
5
|
+
// report. Serve `@mythicalos/ui-core/styles.css` (after `@mythicalos/tokens`) so these components'
|
|
6
|
+
// classes resolve — this package ships no CSS of its own.
|
|
7
|
+
export { Button, buttonClass } from "./Button.js";
|
|
8
|
+
export { Input, Toggle, Checkbox } from "./Input.js";
|
|
9
|
+
export { MaskedSecretInput } from "./MaskedSecretInput.js";
|
|
10
|
+
export { EmptyState } from "./EmptyState.js";
|
|
11
|
+
export { Scrim, ConfirmDialog, typedNameMatches, BULLET_ICON, } from "./ConfirmDialog.js";
|
|
12
|
+
export { Toast, ToastProvider, composeToastText, TOAST_SEP, } from "./Toast.js";
|
|
13
|
+
export { ToastContext, useToast, } from "./toast-context.js";
|
|
14
|
+
export { usePoll, useInterval, shouldResetEpoch, nextPollDelay, makePollEpochGuard, runPollTick, POLL_JITTER_RATIO, POLL_BACKOFF_CAP_MS, } from "./hooks.js";
|
|
15
|
+
// ── the 7 new atoms (design-export's "proposed additions" — Task 6 graduation) ──
|
|
16
|
+
export { Chip, chipClass } from "./Chip.js";
|
|
17
|
+
export { Card } from "./Card.js";
|
|
18
|
+
export { Avatar } from "./Avatar.js";
|
|
19
|
+
export { StatusLine, statusLineClass } from "./StatusLine.js";
|
|
20
|
+
export { SearchInput } from "./SearchInput.js";
|
|
21
|
+
export { Banner, bannerClass, BANNER_ICON } from "./Banner.js";
|
|
22
|
+
export { Gauge, gaugeTone, gaugeGeom } from "./Gauge.js";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ToastStatus, type ToastSpec, type ToastBus } from "@mythicalos/ui-core/logic";
|
|
2
|
+
export { type ToastStatus, type ToastSpec, type ToastBus };
|
|
3
|
+
export declare const ToastContext: import("react").Context<ToastBus>;
|
|
4
|
+
export declare function useToast(): ToastBus;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// @mythicalos/react-ui — the toast bus context (decoupled from any app API client). Shared with the
|
|
2
|
+
// Preact sibling so both family UIs can push toasts through one implementation shape. The app
|
|
3
|
+
// provides <ToastProvider> at its root and components push via useToast().
|
|
4
|
+
//
|
|
5
|
+
// React twin of packages/preact-ui/src/toast-context.ts. `ToastStatus`/`ToastSpec`/`ToastBus` are
|
|
6
|
+
// imported from `@mythicalos/ui-core` (framework-agnostic); only the `createContext`/`useContext`
|
|
7
|
+
// wiring differs — `react` instead of `preact`/`preact/hooks`.
|
|
8
|
+
import { createContext, useContext } from "react";
|
|
9
|
+
import {} from "@mythicalos/ui-core/logic";
|
|
10
|
+
export {};
|
|
11
|
+
export const ToastContext = createContext({ push: () => { } });
|
|
12
|
+
export function useToast() {
|
|
13
|
+
return useContext(ToastContext);
|
|
14
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mythicalos/react-ui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "mythicalOS thin React bindings over @mythicalos/ui-core — Button, Input/Toggle/Checkbox, MaskedSecretInput, EmptyState, ConfirmDialog/Scrim, Toast/ToastProvider, Chip, Card, Avatar, StatusLine, SearchInput, Banner, Gauge, and the usePoll/useInterval hooks. Render + framework wiring only — every class string, poll-scheduling math, glyph map, and text composition is derived by @mythicalos/ui-core so this binding and its Preact sibling can never drift. Apache-2.0.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/mythicalOS/mythical-ui.git",
|
|
14
|
+
"directory": "packages/react-ui"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/mythicalOS/mythical-ui#packages",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": ["dist", "README.md", "NOTICE"],
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"react": "^18.2.0 || ^19",
|
|
26
|
+
"react-dom": "^18.2.0 || ^19",
|
|
27
|
+
"@mythicalos/tokens": "^0.5.11"
|
|
28
|
+
},
|
|
29
|
+
"peerDependenciesMeta": {
|
|
30
|
+
"@mythicalos/tokens": {
|
|
31
|
+
"optional": true
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@mythicalos/ui-core": "^0.1.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc -p tsconfig.build.json",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"test": "bun test",
|
|
41
|
+
"prepublishOnly": "bun run build && bun test && bun run typecheck"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"react": "^19.2.8",
|
|
45
|
+
"react-dom": "^19.2.8",
|
|
46
|
+
"@types/react": "^19",
|
|
47
|
+
"@types/react-dom": "^19",
|
|
48
|
+
"typescript": "^5"
|
|
49
|
+
}
|
|
50
|
+
}
|