@domainkit/react 0.8.0 → 0.9.1
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 +128 -36
- package/dist/index.d.mts +1219 -1308
- package/dist/index.mjs +2016 -1614
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +1185 -1023
- package/package.json +2 -10
package/README.md
CHANGED
|
@@ -1,72 +1,164 @@
|
|
|
1
1
|
# @domainkit/react
|
|
2
2
|
|
|
3
|
-
React flows
|
|
4
|
-
|
|
5
|
-
The package can render the full domain flow, one focused flow, or DNS records. Every stateful
|
|
6
|
-
operation goes through your authenticated server transport; provider credentials never belong in the
|
|
7
|
-
browser.
|
|
3
|
+
React flows for custom-domain setup, over a transport your server owns. Provider credentials never
|
|
4
|
+
reach the browser.
|
|
8
5
|
|
|
9
6
|
## Install
|
|
10
7
|
|
|
11
8
|
```sh
|
|
12
|
-
npm install @domainkit/react domainkit effect@rc
|
|
9
|
+
npm install @domainkit/react domainkit effect@rc react react-dom
|
|
13
10
|
```
|
|
14
11
|
|
|
15
12
|
React 19 is required. Install `domainkit` and `@domainkit/react` at the same release version.
|
|
16
13
|
|
|
17
|
-
##
|
|
14
|
+
## Two components
|
|
18
15
|
|
|
19
16
|
```tsx
|
|
17
|
+
import { DnsRecord } from "domainkit";
|
|
18
|
+
import { Transport } from "domainkit/client";
|
|
20
19
|
import { Domain, DomainKit } from "@domainkit/react";
|
|
21
20
|
import "@domainkit/react/styles.css";
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
const transport = Transport.fromFetch("/api/domainkit");
|
|
23
|
+
|
|
24
|
+
const requirements = [
|
|
25
|
+
DnsRecord.cname({ name: "app.example.com", target: "edge.acme.dev", purpose: "Serve your site" }),
|
|
26
|
+
DnsRecord.txt({
|
|
27
|
+
name: "_acme.app.example.com",
|
|
28
|
+
value: "acme-verify=7f3a",
|
|
29
|
+
purpose: "Prove ownership",
|
|
30
|
+
}),
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export function DomainSettings() {
|
|
24
34
|
return (
|
|
25
|
-
<DomainKit.Root transport={transport}>
|
|
26
|
-
<Domain.Flow domain="example.com"
|
|
35
|
+
<DomainKit.Root transport={transport} colorScheme="inherit">
|
|
36
|
+
<Domain.Flow domain="app.example.com" requirements={requirements} />
|
|
27
37
|
</DomainKit.Root>
|
|
28
38
|
);
|
|
29
39
|
}
|
|
30
40
|
```
|
|
31
41
|
|
|
32
|
-
`Domain.Flow`
|
|
33
|
-
|
|
34
|
-
|
|
42
|
+
`Domain.Flow` connects a provider, plans the DNS changes, takes the customer's approval or refusal,
|
|
43
|
+
applies the plan, observes the records, and cleans them up against the apply receipt. It renders
|
|
44
|
+
only the capability groups the transport declares.
|
|
45
|
+
|
|
46
|
+
`Transport.fromFetch` points at the routes you mounted from `domainkit/server`. Writing it inline in
|
|
47
|
+
JSX is fine: `DomainKit.Root` keeps the transport's identity for the whole mount, so the controllers
|
|
48
|
+
do not restart on every render. Pass `revision` to re-inspect every mounted domain after a change
|
|
49
|
+
the UI did not make.
|
|
35
50
|
|
|
36
|
-
|
|
51
|
+
An interactive provider returns the customer to the page they started from. Pass `returnTo` to name
|
|
52
|
+
a different destination, or `null` to leave the server's `defaultReturnTo` in charge.
|
|
53
|
+
|
|
54
|
+
## Own one piece, keep the rest
|
|
55
|
+
|
|
56
|
+
Every part of the flow is a slot with a default.
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
<Domain.Flow
|
|
60
|
+
domain="app.example.com"
|
|
61
|
+
requirements={requirements}
|
|
62
|
+
slots={{
|
|
63
|
+
records: ({ records, readiness }) => <MyTable records={records} readiness={readiness} />,
|
|
64
|
+
}}
|
|
65
|
+
onApplied={(receipt) => track("dns.applied", { receiptId: receipt.id })}
|
|
66
|
+
/>
|
|
67
|
+
```
|
|
37
68
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
69
|
+
| Slot | Receives | Default |
|
|
70
|
+
| -------------- | ----------------------------------------------- | ---------------------------------------------------------- |
|
|
71
|
+
| `connection` | `{ controller, domain }` | `Connect.Card` once connected, `Connect.Dialog` until then |
|
|
72
|
+
| `records` | `{ records, readiness, controller, domain }` | `Records.Table` |
|
|
73
|
+
| `verification` | `{ controller, domain }` | `Verify.Status` with per-requirement evidence |
|
|
74
|
+
| `actions` | `{ connection, provisioning, cleanup, domain }` | Review changes, Approve, Decline, Remove records |
|
|
43
75
|
|
|
44
|
-
|
|
45
|
-
|
|
76
|
+
`Domain.Flow` adds no layout container around a slot: its output is a direct child of the flow root,
|
|
77
|
+
so your own grid can place it without `display: contents`.
|
|
46
78
|
|
|
47
|
-
##
|
|
79
|
+
## The member view
|
|
48
80
|
|
|
49
|
-
|
|
50
|
-
|
|
81
|
+
A customer who may read a domain but not change it gets `readOnly`. Status renders; every control
|
|
82
|
+
that would start a write does not.
|
|
51
83
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
cleanup: api.cleanup,
|
|
57
|
-
});
|
|
84
|
+
```tsx
|
|
85
|
+
<DomainKit.Root transport={transport} readOnly>
|
|
86
|
+
<Domain.Flow domain="app.example.com" requirements={requirements} />
|
|
87
|
+
</DomainKit.Root>
|
|
58
88
|
```
|
|
59
89
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
90
|
+
`Domain.Flow` takes the same flag when only one domain on the page is read-only, and `useReadOnly()`
|
|
91
|
+
tells a part of your own which mode it is in. Capability gating is the other half: a group the
|
|
92
|
+
transport does not declare never renders, so a transport built with
|
|
93
|
+
`Transport.fromFetch(url, { capabilities: ["connection", "verification"] })` already hides
|
|
94
|
+
provisioning and cleanup. `readOnly` covers the authorization a transport cannot express, such as a
|
|
95
|
+
member of an organisation who reaches the same routes.
|
|
96
|
+
|
|
97
|
+
Verification does not wait for a connection: the flow observes the requirements it was given, so a
|
|
98
|
+
domain with no provider attached still reports which records are in place.
|
|
99
|
+
|
|
100
|
+
Observation stays available in read-only, because checking DNS reads the world rather than changing
|
|
101
|
+
the domain. Retrying is not: a flow that becomes read-only after a write failed keeps the failure on
|
|
102
|
+
screen and drops the retry.
|
|
103
|
+
If your `Identity.authorize` denies `observe` to members, pass `slots={{ verification: () => null }}`.
|
|
104
|
+
|
|
105
|
+
## Controllers
|
|
106
|
+
|
|
107
|
+
Compose the flow yourself from the same hooks. Each takes one options object and returns a named
|
|
108
|
+
`Controller` whose `State` is a tagged union.
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
const connection = Connect.useController({ domain });
|
|
112
|
+
const provisioning = Provision.useController({ domain, requirements, onApplied });
|
|
113
|
+
const cleanup = Cleanup.useController({ domain, receiptId, onCleaned });
|
|
114
|
+
const verification = Verify.useController({ domain, polling: true });
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`approve` authorizes the plan digest and applies it. `reject` records the customer's refusal; the
|
|
118
|
+
attempt is terminal and a new plan is needed. `retry` builds a new plan when the reason says the old
|
|
119
|
+
one is gone, and re-runs the failed step otherwise.
|
|
120
|
+
|
|
121
|
+
## Presentation
|
|
122
|
+
|
|
123
|
+
`DomainKit.Root` takes `messages`, `marks`, `icons`, `theme`, `colorScheme`, and `portalContainer`,
|
|
124
|
+
so branding stays in your app. `Messages.Catalog` holds every user-visible string, including one
|
|
125
|
+
sentence per `DomainKit.Error` reason; nothing renders a tag. Provider artwork comes from `marks`,
|
|
126
|
+
with the provider's initial as the fallback and no request at render time. The stylesheet is
|
|
127
|
+
opt-in and every color is a `--domainkit-*` custom property.
|
|
128
|
+
|
|
129
|
+
Every rule ships inside `@layer domainkit`. An unlayered rule of yours already beats every layer, so
|
|
130
|
+
plain CSS needs nothing here.
|
|
131
|
+
|
|
132
|
+
Once your app uses layers, name the order yourself: a layer's priority is its position in that list,
|
|
133
|
+
and a layer you never declare lands wherever it first appears, so `domainkit` can sort under
|
|
134
|
+
Tailwind's preflight and the package's buttons render as bare text.
|
|
135
|
+
|
|
136
|
+
```css
|
|
137
|
+
@layer theme, base, components, domainkit, utilities;
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`domainkit` sits after `base` so preflight cannot strip it, and before `utilities` so a utility class
|
|
141
|
+
still wins. Any layer you declare after it wins the same way:
|
|
142
|
+
|
|
143
|
+
```css
|
|
144
|
+
@layer utilities {
|
|
145
|
+
[data-domainkit-part="records-panel"] {
|
|
146
|
+
border-radius: 0;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Every dialog and popover takes a `render` prop that replaces the surface with your own.
|
|
152
|
+
|
|
153
|
+
## Next.js
|
|
154
|
+
|
|
155
|
+
The package entry carries `"use client"`. A transport is an object of functions, so build it inside
|
|
156
|
+
a client module rather than passing one from a server component.
|
|
64
157
|
|
|
65
158
|
## Learn more
|
|
66
159
|
|
|
67
160
|
- [React source](https://github.com/AryaLabsHQ/domainkit/tree/main/packages/react)
|
|
68
|
-
- [
|
|
69
|
-
- [Application transport contract](https://github.com/AryaLabsHQ/domainkit/blob/main/packages/domainkit/src/transport.ts)
|
|
161
|
+
- [Transport contract](https://github.com/AryaLabsHQ/domainkit/blob/main/packages/domainkit/src/Transport.ts)
|
|
70
162
|
- [Issues](https://github.com/AryaLabsHQ/domainkit/issues)
|
|
71
163
|
|
|
72
164
|
## License
|