@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 CHANGED
@@ -1,72 +1,164 @@
1
1
  # @domainkit/react
2
2
 
3
- React flows and UI parts for DomainKit.
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 @effect/atom-react@rc react react-dom
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
- ## Complete flow
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
- export function DomainSetup() {
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" records={records} />
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` handles connection, plan review, apply, verification, cleanup, and removal of the
33
- domain attachment. Your app still owns authentication, authorization, saved attempts, provider
34
- credentials, and the server-side `Transport` implementation.
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
- ## Choose the parts you need
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
- - `Domain.Flow` — the full lifecycle;
39
- - `Connection.Flow`, `Provisioning.Flow`, and `Cleanup.Flow` — focused flows;
40
- - `Verification`'s model and parts — provider and public-DNS evidence with an observe action;
41
- - `useModel` hooks and parts — your own layout and controls;
42
- - `Records.Table`, `Records.Card`, and record parts — DNS display without a root or transport.
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
- The packaged flows use the same exported models and parts. Components support server rendering;
45
- clipboard, download, and navigation behavior runs only from browser interactions.
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
- ## Host transport
79
+ ## The member view
48
80
 
49
- ```ts
50
- import { Transport } from "domainkit";
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
- export const transport = Transport.layerFromAsync({
53
- connection: api.connections,
54
- provisioning: api.provisioning,
55
- verification: api.verification,
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
- The stylesheet is opt-in. `DomainKit.Root` accepts your messages, provider marks, icons, design
61
- tokens, color scheme, and portal container, so branding stays in your app.
62
- Known provider marks load from `integrations.sh` and fall back to the provider's initial when a mark
63
- is unavailable. Replace them through the `marks` prop when your app owns the artwork.
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
- - [React component catalog](https://domain-kit.dev/components)
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