@guard-rail/core 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/README.md +339 -0
- package/dist/adapters/supabase/auth.d.ts +9 -0
- package/dist/adapters/supabase/auth.js +57 -0
- package/dist/adapters/supabase/auth.js.map +1 -0
- package/dist/adapters/supabase/client.d.ts +28 -0
- package/dist/adapters/supabase/client.js +44 -0
- package/dist/adapters/supabase/client.js.map +1 -0
- package/dist/adapters/supabase/confirm.d.ts +19 -0
- package/dist/adapters/supabase/confirm.js +27 -0
- package/dist/adapters/supabase/confirm.js.map +1 -0
- package/dist/adapters/supabase/db.d.ts +9 -0
- package/dist/adapters/supabase/db.js +49 -0
- package/dist/adapters/supabase/db.js.map +1 -0
- package/dist/adapters/supabase/index.d.ts +25 -0
- package/dist/adapters/supabase/index.js +29 -0
- package/dist/adapters/supabase/index.js.map +1 -0
- package/dist/adapters/supabase/members.d.ts +9 -0
- package/dist/adapters/supabase/members.js +73 -0
- package/dist/adapters/supabase/members.js.map +1 -0
- package/dist/adapters/supabase/repeaters.d.ts +9 -0
- package/dist/adapters/supabase/repeaters.js +73 -0
- package/dist/adapters/supabase/repeaters.js.map +1 -0
- package/dist/adapters/supabase/storage.d.ts +18 -0
- package/dist/adapters/supabase/storage.js +93 -0
- package/dist/adapters/supabase/storage.js.map +1 -0
- package/dist/core/adapter-types.d.ts +212 -0
- package/dist/core/adapter-types.js +1 -0
- package/dist/core/adapter-types.js.map +1 -0
- package/dist/core/bailout.d.ts +9 -0
- package/dist/core/bailout.js +14 -0
- package/dist/core/bailout.js.map +1 -0
- package/dist/core/config.d.ts +27 -0
- package/dist/core/config.js +46 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/defs.d.ts +22 -0
- package/dist/core/defs.js +16 -0
- package/dist/core/defs.js.map +1 -0
- package/dist/core/types.d.ts +136 -0
- package/dist/core/types.js +1 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/react/Admin.d.ts +8 -0
- package/dist/react/Admin.js +116 -0
- package/dist/react/Admin.js.map +1 -0
- package/dist/react/GRail/client.d.ts +11 -0
- package/dist/react/GRail/client.js +242 -0
- package/dist/react/GRail/client.js.map +1 -0
- package/dist/react/GRail/index.d.ts +12 -0
- package/dist/react/GRail/index.js +19 -0
- package/dist/react/GRail/index.js.map +1 -0
- package/dist/react/GRail/render.d.ts +23 -0
- package/dist/react/GRail/render.js +67 -0
- package/dist/react/GRail/render.js.map +1 -0
- package/dist/react/Icons.d.ts +13 -0
- package/dist/react/Icons.js +68 -0
- package/dist/react/Icons.js.map +1 -0
- package/dist/react/MediaPicker.d.ts +14 -0
- package/dist/react/MediaPicker.js +172 -0
- package/dist/react/MediaPicker.js.map +1 -0
- package/dist/react/Provider.d.ts +11 -0
- package/dist/react/Provider.js +26 -0
- package/dist/react/Provider.js.map +1 -0
- package/dist/react/Repeater/client.d.ts +16 -0
- package/dist/react/Repeater/client.js +148 -0
- package/dist/react/Repeater/client.js.map +1 -0
- package/dist/react/Repeater/index.d.ts +35 -0
- package/dist/react/Repeater/index.js +22 -0
- package/dist/react/Repeater/index.js.map +1 -0
- package/dist/react/Syncer.d.ts +17 -0
- package/dist/react/Syncer.js +23 -0
- package/dist/react/Syncer.js.map +1 -0
- package/dist/react/Team.d.ts +7 -0
- package/dist/react/Team.js +190 -0
- package/dist/react/Team.js.map +1 -0
- package/dist/react/resizeImage.d.ts +25 -0
- package/dist/react/resizeImage.js +52 -0
- package/dist/react/resizeImage.js.map +1 -0
- package/dist/react/styles.d.ts +421 -0
- package/dist/react/styles.js +402 -0
- package/dist/react/styles.js.map +1 -0
- package/dist/react/useAnchorRect.d.ts +17 -0
- package/dist/react/useAnchorRect.js +32 -0
- package/dist/react/useAnchorRect.js.map +1 -0
- package/dist/server/actions.d.ts +41 -0
- package/dist/server/actions.js +214 -0
- package/dist/server/actions.js.map +1 -0
- package/dist/server/data.d.ts +10 -0
- package/dist/server/data.js +24 -0
- package/dist/server/data.js.map +1 -0
- package/dist/server/fieldData.d.ts +26 -0
- package/dist/server/fieldData.js +9 -0
- package/dist/server/fieldData.js.map +1 -0
- package/dist/server/loader.d.ts +6 -0
- package/dist/server/loader.js +53 -0
- package/dist/server/loader.js.map +1 -0
- package/dist/server/repeaterActions.d.ts +20 -0
- package/dist/server/repeaterActions.js +72 -0
- package/dist/server/repeaterActions.js.map +1 -0
- package/dist/server/repeaterData.d.ts +18 -0
- package/dist/server/repeaterData.js +27 -0
- package/dist/server/repeaterData.js.map +1 -0
- package/dist/server/session.d.ts +14 -0
- package/dist/server/session.js +37 -0
- package/dist/server/session.js.map +1 -0
- package/dist/server/wrapper.d.ts +12 -0
- package/dist/server/wrapper.js +42 -0
- package/dist/server/wrapper.js.map +1 -0
- package/package.json +58 -0
- package/supabase/migrations/20260911000000_guardrail_init.sql +288 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import React, { useRef, useState } from "react";
|
|
4
|
+
import { saveOverride } from "../../server/actions";
|
|
5
|
+
import { GRailRender } from "./render";
|
|
6
|
+
import { useGRail } from "../Provider";
|
|
7
|
+
import { useAnchorRect } from "../useAnchorRect";
|
|
8
|
+
import { CheckIcon, CloseIcon, SpinnerIcon } from "../Icons";
|
|
9
|
+
import { S } from "../styles";
|
|
10
|
+
import { MediaPicker } from "../MediaPicker";
|
|
11
|
+
function GRailClient({
|
|
12
|
+
as,
|
|
13
|
+
initialOverride,
|
|
14
|
+
...htmlProps
|
|
15
|
+
}) {
|
|
16
|
+
const { isEditMode } = useGRail();
|
|
17
|
+
const [editing, setEditing] = useState(false);
|
|
18
|
+
const [saving, setSaving] = useState(false);
|
|
19
|
+
const [error, setError] = useState(null);
|
|
20
|
+
const [row, setRow] = useState(initialOverride);
|
|
21
|
+
const elementRef = useRef(null);
|
|
22
|
+
const rect = useAnchorRect(elementRef, editing);
|
|
23
|
+
const sessionTextRef = useRef(initialOverride.value.text ?? "");
|
|
24
|
+
const draftTextRef = useRef(initialOverride.value.text ?? "");
|
|
25
|
+
if (!isEditMode) {
|
|
26
|
+
return /* @__PURE__ */ jsx(GRailRender, { as, ...htmlProps, override: row.value });
|
|
27
|
+
}
|
|
28
|
+
const isInline = row.value.type === "text" || row.value.type === "button";
|
|
29
|
+
const beginEdit = () => {
|
|
30
|
+
sessionTextRef.current = row.value.text ?? "";
|
|
31
|
+
draftTextRef.current = row.value.text ?? "";
|
|
32
|
+
setError(null);
|
|
33
|
+
setEditing(true);
|
|
34
|
+
};
|
|
35
|
+
const updateAttribute = (key, value) => setRow((prev) => ({ ...prev, value: { ...prev.value, [key]: value } }));
|
|
36
|
+
const patchValue = (patch) => setRow((prev) => ({ ...prev, value: { ...prev.value, ...patch } }));
|
|
37
|
+
const handleSave = async () => {
|
|
38
|
+
const next = isInline ? { ...row, value: { ...row.value, text: draftTextRef.current } } : row;
|
|
39
|
+
setSaving(true);
|
|
40
|
+
setError(null);
|
|
41
|
+
const res = await saveOverride(next);
|
|
42
|
+
setSaving(false);
|
|
43
|
+
if (!res.success) {
|
|
44
|
+
setError(res.error ?? "Save failed.");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
setRow(next);
|
|
48
|
+
setEditing(false);
|
|
49
|
+
};
|
|
50
|
+
const handleCancel = () => {
|
|
51
|
+
setRow(initialOverride);
|
|
52
|
+
draftTextRef.current = initialOverride.value.text ?? "";
|
|
53
|
+
if (isInline && elementRef.current) {
|
|
54
|
+
elementRef.current.textContent = initialOverride.value.text ?? "";
|
|
55
|
+
}
|
|
56
|
+
setError(null);
|
|
57
|
+
setEditing(false);
|
|
58
|
+
};
|
|
59
|
+
const renderedValue = editing && isInline ? { ...row.value, text: sessionTextRef.current } : row.value;
|
|
60
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
61
|
+
/* @__PURE__ */ jsx(
|
|
62
|
+
GRailRender,
|
|
63
|
+
{
|
|
64
|
+
as,
|
|
65
|
+
...htmlProps,
|
|
66
|
+
ref: elementRef,
|
|
67
|
+
override: renderedValue,
|
|
68
|
+
style: {
|
|
69
|
+
...htmlProps.style,
|
|
70
|
+
outline: editing ? "2px solid #2563eb" : "1px dashed #60a5fa",
|
|
71
|
+
outlineOffset: 2,
|
|
72
|
+
cursor: editing ? "text" : "pointer"
|
|
73
|
+
},
|
|
74
|
+
onClick: (e) => {
|
|
75
|
+
if (editing) return;
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
e.stopPropagation();
|
|
78
|
+
beginEdit();
|
|
79
|
+
},
|
|
80
|
+
...isInline ? {
|
|
81
|
+
contentEditable: editing,
|
|
82
|
+
suppressContentEditableWarning: true,
|
|
83
|
+
onInput: (e) => {
|
|
84
|
+
draftTextRef.current = e.currentTarget.textContent ?? "";
|
|
85
|
+
},
|
|
86
|
+
onKeyDown: (e) => {
|
|
87
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
void handleSave();
|
|
90
|
+
} else if (e.key === "Escape") {
|
|
91
|
+
e.preventDefault();
|
|
92
|
+
handleCancel();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} : {}
|
|
96
|
+
}
|
|
97
|
+
),
|
|
98
|
+
editing && rect && /* @__PURE__ */ jsx(
|
|
99
|
+
Chrome,
|
|
100
|
+
{
|
|
101
|
+
rect,
|
|
102
|
+
isInline,
|
|
103
|
+
row,
|
|
104
|
+
error,
|
|
105
|
+
saving,
|
|
106
|
+
onChange: updateAttribute,
|
|
107
|
+
onPatch: patchValue,
|
|
108
|
+
onSave: handleSave,
|
|
109
|
+
onCancel: handleCancel
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
] });
|
|
113
|
+
}
|
|
114
|
+
function Chrome({
|
|
115
|
+
rect,
|
|
116
|
+
isInline,
|
|
117
|
+
row,
|
|
118
|
+
error,
|
|
119
|
+
saving,
|
|
120
|
+
onChange,
|
|
121
|
+
onPatch,
|
|
122
|
+
onSave,
|
|
123
|
+
onCancel
|
|
124
|
+
}) {
|
|
125
|
+
const { storage } = useGRail();
|
|
126
|
+
const [pickerOpen, setPickerOpen] = useState(false);
|
|
127
|
+
const GAP = 8;
|
|
128
|
+
const below = rect.bottom + GAP;
|
|
129
|
+
const flip = typeof window !== "undefined" && below > window.innerHeight - 120;
|
|
130
|
+
const position = {
|
|
131
|
+
left: Math.max(8, rect.left),
|
|
132
|
+
...flip ? { top: Math.max(8, rect.top - GAP - 44) } : { top: below }
|
|
133
|
+
};
|
|
134
|
+
const actions = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
135
|
+
/* @__PURE__ */ jsx(
|
|
136
|
+
"button",
|
|
137
|
+
{
|
|
138
|
+
style: S.iconBtnPrimary,
|
|
139
|
+
onClick: onSave,
|
|
140
|
+
disabled: saving,
|
|
141
|
+
title: "Save (Enter)",
|
|
142
|
+
"aria-label": "Save",
|
|
143
|
+
children: saving ? /* @__PURE__ */ jsx(SpinnerIcon, {}) : /* @__PURE__ */ jsx(CheckIcon, {})
|
|
144
|
+
}
|
|
145
|
+
),
|
|
146
|
+
/* @__PURE__ */ jsx(
|
|
147
|
+
"button",
|
|
148
|
+
{
|
|
149
|
+
style: S.iconBtn,
|
|
150
|
+
onClick: onCancel,
|
|
151
|
+
disabled: saving,
|
|
152
|
+
title: "Cancel (Esc)",
|
|
153
|
+
"aria-label": "Cancel",
|
|
154
|
+
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
] });
|
|
158
|
+
if (isInline) {
|
|
159
|
+
return /* @__PURE__ */ jsxs("div", { "data-guardrail": true, style: { ...S.anchoredBar, ...position }, children: [
|
|
160
|
+
actions,
|
|
161
|
+
error && /* @__PURE__ */ jsx("span", { style: { ...S.error, margin: "0 6px" }, children: error })
|
|
162
|
+
] });
|
|
163
|
+
}
|
|
164
|
+
return /* @__PURE__ */ jsxs(
|
|
165
|
+
"div",
|
|
166
|
+
{
|
|
167
|
+
"data-guardrail": true,
|
|
168
|
+
style: { ...S.anchoredPanel, ...position },
|
|
169
|
+
onClick: (e) => e.stopPropagation(),
|
|
170
|
+
children: [
|
|
171
|
+
row.value.type === "image" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
172
|
+
row.value.src && // Plain img: previews must not depend on remotePatterns.
|
|
173
|
+
/* @__PURE__ */ jsx("img", { src: row.value.src, alt: "", style: S.previewThumb }),
|
|
174
|
+
storage && /* @__PURE__ */ jsx(
|
|
175
|
+
"button",
|
|
176
|
+
{
|
|
177
|
+
type: "button",
|
|
178
|
+
style: S.secondaryBtn,
|
|
179
|
+
onClick: () => setPickerOpen(true),
|
|
180
|
+
children: row.value.src ? "Replace image" : "Choose image"
|
|
181
|
+
}
|
|
182
|
+
),
|
|
183
|
+
/* @__PURE__ */ jsx("label", { style: S.label, children: "Alt text" }),
|
|
184
|
+
/* @__PURE__ */ jsx(
|
|
185
|
+
"input",
|
|
186
|
+
{
|
|
187
|
+
style: S.field,
|
|
188
|
+
value: row.value.alt || "",
|
|
189
|
+
placeholder: "Describe the image for screen readers",
|
|
190
|
+
onChange: (e) => onChange("alt", e.target.value)
|
|
191
|
+
}
|
|
192
|
+
),
|
|
193
|
+
!storage && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
194
|
+
/* @__PURE__ */ jsx("label", { style: S.label, children: "Image URL" }),
|
|
195
|
+
/* @__PURE__ */ jsx(
|
|
196
|
+
"input",
|
|
197
|
+
{
|
|
198
|
+
style: S.field,
|
|
199
|
+
value: row.value.src || "",
|
|
200
|
+
onChange: (e) => onPatch({ src: e.target.value, width: void 0, height: void 0 })
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
] }),
|
|
204
|
+
pickerOpen && /* @__PURE__ */ jsx(
|
|
205
|
+
MediaPicker,
|
|
206
|
+
{
|
|
207
|
+
current: row.value.src,
|
|
208
|
+
onClose: () => setPickerOpen(false),
|
|
209
|
+
onSelect: (image) => onPatch({ src: image.src, width: image.width, height: image.height })
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
] }),
|
|
213
|
+
row.value.type === "link" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
214
|
+
/* @__PURE__ */ jsx("label", { style: S.label, children: "Link text" }),
|
|
215
|
+
/* @__PURE__ */ jsx(
|
|
216
|
+
"input",
|
|
217
|
+
{
|
|
218
|
+
style: S.field,
|
|
219
|
+
value: row.value.text || "",
|
|
220
|
+
onChange: (e) => onChange("text", e.target.value)
|
|
221
|
+
}
|
|
222
|
+
),
|
|
223
|
+
/* @__PURE__ */ jsx("label", { style: S.label, children: "URL" }),
|
|
224
|
+
/* @__PURE__ */ jsx(
|
|
225
|
+
"input",
|
|
226
|
+
{
|
|
227
|
+
style: S.field,
|
|
228
|
+
value: row.value.href || "",
|
|
229
|
+
onChange: (e) => onChange("href", e.target.value)
|
|
230
|
+
}
|
|
231
|
+
)
|
|
232
|
+
] }),
|
|
233
|
+
error && /* @__PURE__ */ jsx("div", { style: { ...S.error, marginBottom: 8 }, children: error }),
|
|
234
|
+
/* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 4, justifyContent: "flex-end" }, children: actions })
|
|
235
|
+
]
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
export {
|
|
240
|
+
GRailClient
|
|
241
|
+
};
|
|
242
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/react/GRail/client.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useRef, useState } from \"react\";\nimport { saveOverride } from \"../../server/actions\";\nimport { GRailRender } from \"./render\";\nimport { useGRail } from \"../Provider\";\nimport { useAnchorRect } from \"../useAnchorRect\";\nimport { CheckIcon, CloseIcon, SpinnerIcon } from \"../Icons\";\nimport { S } from \"../styles\";\nimport { MediaPicker } from \"../MediaPicker\";\nimport type { HTMLKeys, OverrideRow, OverrideValue } from \"../../core/types\";\n\nexport function GRailClient({\n as,\n initialOverride,\n ...htmlProps\n}: {\n as?: HTMLKeys;\n initialOverride: OverrideRow;\n [key: string]: unknown;\n}) {\n const { isEditMode } = useGRail();\n const [editing, setEditing] = useState(false);\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [row, setRow] = useState<OverrideRow>(initialOverride);\n\n const elementRef = useRef<HTMLElement | null>(null);\n const rect = useAnchorRect(elementRef, editing);\n\n /**\n * The text the element was rendered with when this edit session began.\n *\n * While editing, the DOM owns the text and React must not touch it. Feeding\n * state back into a contentEditable's children makes React replace the text\n * node on every keystroke, which resets the caret to offset 0 — typing then\n * appears reversed and backspace jumps to the start. Holding this value\n * constant means the children prop never changes, so React leaves the node\n * alone and the caret stays put.\n */\n const sessionTextRef = useRef<string>(initialOverride.value.text ?? \"\");\n /** Live DOM text, updated without re-rendering. Read on save. */\n const draftTextRef = useRef<string>(initialOverride.value.text ?? \"\");\n\n if (!isEditMode) {\n return <GRailRender as={as} {...htmlProps} override={row.value} />;\n }\n\n const isInline = row.value.type === \"text\" || row.value.type === \"button\";\n\n const beginEdit = () => {\n sessionTextRef.current = row.value.text ?? \"\";\n draftTextRef.current = row.value.text ?? \"\";\n setError(null);\n setEditing(true);\n };\n\n const updateAttribute = (key: string, value: string) =>\n setRow((prev) => ({ ...prev, value: { ...prev.value, [key]: value } }));\n\n const patchValue = (patch: Partial<OverrideValue>) =>\n setRow((prev) => ({ ...prev, value: { ...prev.value, ...patch } }));\n\n const handleSave = async () => {\n // Inline text lives in the DOM, not in state, so read it back here.\n const next: OverrideRow = isInline\n ? { ...row, value: { ...row.value, text: draftTextRef.current } }\n : row;\n\n setSaving(true);\n setError(null);\n const res = await saveOverride(next);\n setSaving(false);\n\n if (!res.success) {\n setError(res.error ?? \"Save failed.\");\n return;\n }\n setRow(next);\n setEditing(false);\n };\n\n const handleCancel = () => {\n setRow(initialOverride);\n draftTextRef.current = initialOverride.value.text ?? \"\";\n if (isInline && elementRef.current) {\n elementRef.current.textContent = initialOverride.value.text ?? \"\";\n }\n setError(null);\n setEditing(false);\n };\n\n // Frozen during the session so React never rewrites the live text node.\n const renderedValue =\n editing && isInline\n ? { ...row.value, text: sessionTextRef.current }\n : row.value;\n\n return (\n <>\n <GRailRender\n as={as}\n {...htmlProps}\n ref={elementRef}\n override={renderedValue}\n style={{\n ...(htmlProps.style as React.CSSProperties),\n outline: editing ? \"2px solid #2563eb\" : \"1px dashed #60a5fa\",\n outlineOffset: 2,\n cursor: editing ? \"text\" : \"pointer\",\n }}\n onClick={(e: React.MouseEvent) => {\n if (editing) return;\n e.preventDefault();\n // preventDefault only blocks the browser's native action (following\n // an href); it does not stop the click from bubbling. A field\n // rendered inside a Link would still trigger the Link's own onClick\n // and navigate anyway — stopPropagation is what actually claims the\n // click for editing instead. (Use `editable={false}` on fields\n // meant to stay pure navigation, e.g. inside a card that links to\n // that content's own page.)\n e.stopPropagation();\n beginEdit();\n }}\n // Spread only for inline text: images and links would receive invalid\n // DOM props (contentEditable on next/image, etc).\n {...(isInline\n ? {\n contentEditable: editing,\n suppressContentEditableWarning: true,\n onInput: (e: React.FormEvent<HTMLElement>) => {\n // Ref only — setState here re-renders and moves the caret.\n draftTextRef.current = e.currentTarget.textContent ?? \"\";\n },\n onKeyDown: (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n void handleSave();\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n handleCancel();\n }\n },\n }\n : {})}\n />\n\n {editing && rect && (\n <Chrome\n rect={rect}\n isInline={isInline}\n row={row}\n error={error}\n saving={saving}\n onChange={updateAttribute}\n onPatch={patchValue}\n onSave={handleSave}\n onCancel={handleCancel}\n />\n )}\n </>\n );\n}\n\nfunction Chrome({\n rect,\n isInline,\n row,\n error,\n saving,\n onChange,\n onPatch,\n onSave,\n onCancel,\n}: {\n rect: { top: number; left: number; bottom: number; width: number };\n isInline: boolean;\n row: OverrideRow;\n error: string | null;\n saving: boolean;\n onChange: (key: string, value: string) => void;\n onPatch: (patch: Partial<OverrideValue>) => void;\n onSave: () => void;\n onCancel: () => void;\n}) {\n const { storage } = useGRail();\n const [pickerOpen, setPickerOpen] = useState(false);\n const GAP = 8;\n // Flip above the element when there is no room below it.\n const below = rect.bottom + GAP;\n const flip = typeof window !== \"undefined\" && below > window.innerHeight - 120;\n\n const position: React.CSSProperties = {\n left: Math.max(8, rect.left),\n ...(flip ? { top: Math.max(8, rect.top - GAP - 44) } : { top: below }),\n };\n\n const actions = (\n <>\n <button\n style={S.iconBtnPrimary}\n onClick={onSave}\n disabled={saving}\n title=\"Save (Enter)\"\n aria-label=\"Save\"\n >\n {saving ? <SpinnerIcon /> : <CheckIcon />}\n </button>\n <button\n style={S.iconBtn}\n onClick={onCancel}\n disabled={saving}\n title=\"Cancel (Esc)\"\n aria-label=\"Cancel\"\n >\n <CloseIcon />\n </button>\n </>\n );\n\n // Text and buttons edit in place, so they only need the two controls.\n if (isInline) {\n return (\n <div data-guardrail style={{ ...S.anchoredBar, ...position }}>\n {actions}\n {error && <span style={{ ...S.error, margin: \"0 6px\" }}>{error}</span>}\n </div>\n );\n }\n\n // Images and links carry attributes with no on-page representation.\n return (\n <div\n data-guardrail\n style={{ ...S.anchoredPanel, ...position }}\n onClick={(e) => e.stopPropagation()}\n >\n {row.value.type === \"image\" && (\n <>\n {row.value.src && (\n // Plain img: previews must not depend on remotePatterns.\n <img src={row.value.src} alt=\"\" style={S.previewThumb} />\n )}\n\n {storage && (\n <button\n type=\"button\"\n style={S.secondaryBtn}\n onClick={() => setPickerOpen(true)}\n >\n {row.value.src ? \"Replace image\" : \"Choose image\"}\n </button>\n )}\n\n <label style={S.label}>Alt text</label>\n <input\n style={S.field}\n value={row.value.alt || \"\"}\n placeholder=\"Describe the image for screen readers\"\n onChange={(e) => onChange(\"alt\", e.target.value)}\n />\n\n {/* Only without storage: there is no picker then, so a URL is the one\n way to set an image. A typed URL has unknown proportions, so any\n stored dimensions are cleared. */}\n {!storage && (\n <>\n <label style={S.label}>Image URL</label>\n <input\n style={S.field}\n value={row.value.src || \"\"}\n onChange={(e) =>\n onPatch({ src: e.target.value, width: undefined, height: undefined })\n }\n />\n </>\n )}\n\n {pickerOpen && (\n <MediaPicker\n current={row.value.src}\n onClose={() => setPickerOpen(false)}\n onSelect={(image) =>\n onPatch({ src: image.src, width: image.width, height: image.height })\n }\n />\n )}\n </>\n )}\n\n {row.value.type === \"link\" && (\n <>\n <label style={S.label}>Link text</label>\n <input\n style={S.field}\n value={row.value.text || \"\"}\n onChange={(e) => onChange(\"text\", e.target.value)}\n />\n <label style={S.label}>URL</label>\n <input\n style={S.field}\n value={row.value.href || \"\"}\n onChange={(e) => onChange(\"href\", e.target.value)}\n />\n </>\n )}\n\n {error && <div style={{ ...S.error, marginBottom: 8 }}>{error}</div>}\n <div style={{ display: \"flex\", gap: 4, justifyContent: \"flex-end\" }}>\n {actions}\n </div>\n </div>\n );\n}\n"],"mappings":";AA6CW,SAsDP,UAtDO,KAsDP,YAtDO;AA3CX,OAAO,SAAS,QAAQ,gBAAgB;AACxC,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAC5B,SAAS,gBAAgB;AACzB,SAAS,qBAAqB;AAC9B,SAAS,WAAW,WAAW,mBAAmB;AAClD,SAAS,SAAS;AAClB,SAAS,mBAAmB;AAGrB,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAIG;AACD,QAAM,EAAE,WAAW,IAAI,SAAS;AAChC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAC1C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AACtD,QAAM,CAAC,KAAK,MAAM,IAAI,SAAsB,eAAe;AAE3D,QAAM,aAAa,OAA2B,IAAI;AAClD,QAAM,OAAO,cAAc,YAAY,OAAO;AAY9C,QAAM,iBAAiB,OAAe,gBAAgB,MAAM,QAAQ,EAAE;AAEtE,QAAM,eAAe,OAAe,gBAAgB,MAAM,QAAQ,EAAE;AAEpE,MAAI,CAAC,YAAY;AACf,WAAO,oBAAC,eAAY,IAAS,GAAG,WAAW,UAAU,IAAI,OAAO;AAAA,EAClE;AAEA,QAAM,WAAW,IAAI,MAAM,SAAS,UAAU,IAAI,MAAM,SAAS;AAEjE,QAAM,YAAY,MAAM;AACtB,mBAAe,UAAU,IAAI,MAAM,QAAQ;AAC3C,iBAAa,UAAU,IAAI,MAAM,QAAQ;AACzC,aAAS,IAAI;AACb,eAAW,IAAI;AAAA,EACjB;AAEA,QAAM,kBAAkB,CAAC,KAAa,UACpC,OAAO,CAAC,UAAU,EAAE,GAAG,MAAM,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,GAAG,MAAM,EAAE,EAAE;AAExE,QAAM,aAAa,CAAC,UAClB,OAAO,CAAC,UAAU,EAAE,GAAG,MAAM,OAAO,EAAE,GAAG,KAAK,OAAO,GAAG,MAAM,EAAE,EAAE;AAEpE,QAAM,aAAa,YAAY;AAE7B,UAAM,OAAoB,WACtB,EAAE,GAAG,KAAK,OAAO,EAAE,GAAG,IAAI,OAAO,MAAM,aAAa,QAAQ,EAAE,IAC9D;AAEJ,cAAU,IAAI;AACd,aAAS,IAAI;AACb,UAAM,MAAM,MAAM,aAAa,IAAI;AACnC,cAAU,KAAK;AAEf,QAAI,CAAC,IAAI,SAAS;AAChB,eAAS,IAAI,SAAS,cAAc;AACpC;AAAA,IACF;AACA,WAAO,IAAI;AACX,eAAW,KAAK;AAAA,EAClB;AAEA,QAAM,eAAe,MAAM;AACzB,WAAO,eAAe;AACtB,iBAAa,UAAU,gBAAgB,MAAM,QAAQ;AACrD,QAAI,YAAY,WAAW,SAAS;AAClC,iBAAW,QAAQ,cAAc,gBAAgB,MAAM,QAAQ;AAAA,IACjE;AACA,aAAS,IAAI;AACb,eAAW,KAAK;AAAA,EAClB;AAGA,QAAM,gBACJ,WAAW,WACP,EAAE,GAAG,IAAI,OAAO,MAAM,eAAe,QAAQ,IAC7C,IAAI;AAEV,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACC,GAAG;AAAA,QACJ,KAAK;AAAA,QACL,UAAU;AAAA,QACV,OAAO;AAAA,UACL,GAAI,UAAU;AAAA,UACd,SAAS,UAAU,sBAAsB;AAAA,UACzC,eAAe;AAAA,UACf,QAAQ,UAAU,SAAS;AAAA,QAC7B;AAAA,QACA,SAAS,CAAC,MAAwB;AAChC,cAAI,QAAS;AACb,YAAE,eAAe;AAQjB,YAAE,gBAAgB;AAClB,oBAAU;AAAA,QACZ;AAAA,QAGC,GAAI,WACD;AAAA,UACE,iBAAiB;AAAA,UACjB,gCAAgC;AAAA,UAChC,SAAS,CAAC,MAAoC;AAE5C,yBAAa,UAAU,EAAE,cAAc,eAAe;AAAA,UACxD;AAAA,UACA,WAAW,CAAC,MAA2B;AACrC,gBAAI,EAAE,QAAQ,WAAW,CAAC,EAAE,UAAU;AACpC,gBAAE,eAAe;AACjB,mBAAK,WAAW;AAAA,YAClB,WAAW,EAAE,QAAQ,UAAU;AAC7B,gBAAE,eAAe;AACjB,2BAAa;AAAA,YACf;AAAA,UACF;AAAA,QACF,IACA,CAAC;AAAA;AAAA,IACP;AAAA,IAEC,WAAW,QACV;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU;AAAA,QACV,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,UAAU;AAAA;AAAA,IACZ;AAAA,KAEJ;AAEJ;AAEA,SAAS,OAAO;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAUG;AACD,QAAM,EAAE,QAAQ,IAAI,SAAS;AAC7B,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAClD,QAAM,MAAM;AAEZ,QAAM,QAAQ,KAAK,SAAS;AAC5B,QAAM,OAAO,OAAO,WAAW,eAAe,QAAQ,OAAO,cAAc;AAE3E,QAAM,WAAgC;AAAA,IACpC,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI;AAAA,IAC3B,GAAI,OAAO,EAAE,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,MAAM;AAAA,EACtE;AAEA,QAAM,UACJ,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE;AAAA,QACT,SAAS;AAAA,QACT,UAAU;AAAA,QACV,OAAM;AAAA,QACN,cAAW;AAAA,QAEV,mBAAS,oBAAC,eAAY,IAAK,oBAAC,aAAU;AAAA;AAAA,IACzC;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE;AAAA,QACT,SAAS;AAAA,QACT,UAAU;AAAA,QACV,OAAM;AAAA,QACN,cAAW;AAAA,QAEX,8BAAC,aAAU;AAAA;AAAA,IACb;AAAA,KACF;AAIF,MAAI,UAAU;AACZ,WACE,qBAAC,SAAI,kBAAc,MAAC,OAAO,EAAE,GAAG,EAAE,aAAa,GAAG,SAAS,GACxD;AAAA;AAAA,MACA,SAAS,oBAAC,UAAK,OAAO,EAAE,GAAG,EAAE,OAAO,QAAQ,QAAQ,GAAI,iBAAM;AAAA,OACjE;AAAA,EAEJ;AAGA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,kBAAc;AAAA,MACd,OAAO,EAAE,GAAG,EAAE,eAAe,GAAG,SAAS;AAAA,MACzC,SAAS,CAAC,MAAM,EAAE,gBAAgB;AAAA,MAEjC;AAAA,YAAI,MAAM,SAAS,WAClB,iCACG;AAAA,cAAI,MAAM;AAAA,UAET,oBAAC,SAAI,KAAK,IAAI,MAAM,KAAK,KAAI,IAAG,OAAO,EAAE,cAAc;AAAA,UAGxD,WACC;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,OAAO,EAAE;AAAA,cACT,SAAS,MAAM,cAAc,IAAI;AAAA,cAEhC,cAAI,MAAM,MAAM,kBAAkB;AAAA;AAAA,UACrC;AAAA,UAGF,oBAAC,WAAM,OAAO,EAAE,OAAO,sBAAQ;AAAA,UAC/B;AAAA,YAAC;AAAA;AAAA,cACC,OAAO,EAAE;AAAA,cACT,OAAO,IAAI,MAAM,OAAO;AAAA,cACxB,aAAY;AAAA,cACZ,UAAU,CAAC,MAAM,SAAS,OAAO,EAAE,OAAO,KAAK;AAAA;AAAA,UACjD;AAAA,UAKC,CAAC,WACA,iCACE;AAAA,gCAAC,WAAM,OAAO,EAAE,OAAO,uBAAS;AAAA,YAChC;AAAA,cAAC;AAAA;AAAA,gBACC,OAAO,EAAE;AAAA,gBACT,OAAO,IAAI,MAAM,OAAO;AAAA,gBACxB,UAAU,CAAC,MACT,QAAQ,EAAE,KAAK,EAAE,OAAO,OAAO,OAAO,QAAW,QAAQ,OAAU,CAAC;AAAA;AAAA,YAExE;AAAA,aACF;AAAA,UAGD,cACC;AAAA,YAAC;AAAA;AAAA,cACC,SAAS,IAAI,MAAM;AAAA,cACnB,SAAS,MAAM,cAAc,KAAK;AAAA,cAClC,UAAU,CAAC,UACT,QAAQ,EAAE,KAAK,MAAM,KAAK,OAAO,MAAM,OAAO,QAAQ,MAAM,OAAO,CAAC;AAAA;AAAA,UAExE;AAAA,WAEJ;AAAA,QAGD,IAAI,MAAM,SAAS,UAClB,iCACE;AAAA,8BAAC,WAAM,OAAO,EAAE,OAAO,uBAAS;AAAA,UAChC;AAAA,YAAC;AAAA;AAAA,cACC,OAAO,EAAE;AAAA,cACT,OAAO,IAAI,MAAM,QAAQ;AAAA,cACzB,UAAU,CAAC,MAAM,SAAS,QAAQ,EAAE,OAAO,KAAK;AAAA;AAAA,UAClD;AAAA,UACA,oBAAC,WAAM,OAAO,EAAE,OAAO,iBAAG;AAAA,UAC1B;AAAA,YAAC;AAAA;AAAA,cACC,OAAO,EAAE;AAAA,cACT,OAAO,IAAI,MAAM,QAAQ;AAAA,cACzB,UAAU,CAAC,MAAM,SAAS,QAAQ,EAAE,OAAO,KAAK;AAAA;AAAA,UAClD;AAAA,WACF;AAAA,QAGD,SAAS,oBAAC,SAAI,OAAO,EAAE,GAAG,EAAE,OAAO,cAAc,EAAE,GAAI,iBAAM;AAAA,QAC9D,oBAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,KAAK,GAAG,gBAAgB,WAAW,GAC/D,mBACH;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { HTMLKeys, PolymorphicProps } from '../../core/types.js';
|
|
3
|
+
import 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* An editable field. Server Component by default: anonymous visitors get plain
|
|
7
|
+
* server-rendered markup with no client JS. The editor only loads for a
|
|
8
|
+
* verified session.
|
|
9
|
+
*/
|
|
10
|
+
declare function GRail<T extends HTMLKeys = "span">(props: PolymorphicProps<T>): Promise<react_jsx_runtime.JSX.Element>;
|
|
11
|
+
|
|
12
|
+
export { GRail };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { GRailClient } from "./client";
|
|
4
|
+
import { GRailRender } from "./render";
|
|
5
|
+
import { fetchOrScaffold } from "../../server/data";
|
|
6
|
+
import { getCachedViewer } from "../../server/session";
|
|
7
|
+
async function GRail(props) {
|
|
8
|
+
const { id, editable = true, ...rest } = props;
|
|
9
|
+
const override = await fetchOrScaffold(id, props);
|
|
10
|
+
const viewer = await getCachedViewer();
|
|
11
|
+
if (editable && viewer && viewer.role) {
|
|
12
|
+
return /* @__PURE__ */ jsx(GRailClient, { ...rest, initialOverride: override });
|
|
13
|
+
}
|
|
14
|
+
return /* @__PURE__ */ jsx(GRailRender, { ...rest, override: override.value });
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
GRail
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/react/GRail/index.tsx"],"sourcesContent":["import React from \"react\";\nimport { GRailClient } from \"./client\";\nimport { GRailRender } from \"./render\";\nimport { fetchOrScaffold } from \"../../server/data\";\nimport { getCachedViewer } from \"../../server/session\";\nimport type { HTMLKeys, PolymorphicProps } from \"../../core/types\";\n\n/**\n * An editable field. Server Component by default: anonymous visitors get plain\n * server-rendered markup with no client JS. The editor only loads for a\n * verified session.\n */\nexport async function GRail<T extends HTMLKeys = \"span\">(\n props: PolymorphicProps<T>,\n) {\n const { id, editable = true, ...rest } = props;\n const override = await fetchOrScaffold(id, props as Record<string, unknown>);\n const viewer = await getCachedViewer();\n\n // Only members who can actually write get the editor, and only when this\n // field allows it — everyone else gets the same server-rendered markup an\n // anonymous visitor sees.\n if (editable && viewer && viewer.role) {\n return <GRailClient {...(rest as object)} initialOverride={override} />;\n }\n\n return <GRailRender {...(rest as object)} override={override.value} />;\n}\n"],"mappings":"AAuBW;AAvBX,OAAO,WAAW;AAClB,SAAS,mBAAmB;AAC5B,SAAS,mBAAmB;AAC5B,SAAS,uBAAuB;AAChC,SAAS,uBAAuB;AAQhC,eAAsB,MACpB,OACA;AACA,QAAM,EAAE,IAAI,WAAW,MAAM,GAAG,KAAK,IAAI;AACzC,QAAM,WAAW,MAAM,gBAAgB,IAAI,KAAgC;AAC3E,QAAM,SAAS,MAAM,gBAAgB;AAKrC,MAAI,YAAY,UAAU,OAAO,MAAM;AACrC,WAAO,oBAAC,eAAa,GAAI,MAAiB,iBAAiB,UAAU;AAAA,EACvE;AAEA,SAAO,oBAAC,eAAa,GAAI,MAAiB,UAAU,SAAS,OAAO;AACtE;","names":[]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { HTMLKeys, OverrideValue } from '../../core/types.js';
|
|
4
|
+
|
|
5
|
+
interface RenderProps {
|
|
6
|
+
as?: HTMLKeys;
|
|
7
|
+
override: OverrideValue;
|
|
8
|
+
children?: React.ReactNode;
|
|
9
|
+
/** Forwarded to the rendered element so the editor can measure it. */
|
|
10
|
+
ref?: React.Ref<HTMLElement>;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Renders a resolved value as plain markup. Shared by the anonymous SSR path
|
|
15
|
+
* and the editor, so what an editor sees matches what ships.
|
|
16
|
+
*
|
|
17
|
+
* Resolved props are applied *after* the spread. The developer's original
|
|
18
|
+
* src/href are still in `rest`, and spreading them last silently masked every
|
|
19
|
+
* saved image and link override.
|
|
20
|
+
*/
|
|
21
|
+
declare function GRailRender({ as, override, children, ref, ...rest }: RenderProps): react_jsx_runtime.JSX.Element | null;
|
|
22
|
+
|
|
23
|
+
export { GRailRender };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import NextImage from "next/image";
|
|
4
|
+
import NextLink from "next/link";
|
|
5
|
+
function intrinsicSize(override, width, height) {
|
|
6
|
+
const { width: nw, height: nh } = override;
|
|
7
|
+
if (nw && nh) {
|
|
8
|
+
if (width) return { width, height: Math.round(width * nh / nw) };
|
|
9
|
+
if (height) return { width: Math.round(height * nw / nh), height };
|
|
10
|
+
return { width: nw, height: nh };
|
|
11
|
+
}
|
|
12
|
+
return { width: width || 500, height: height || 500 };
|
|
13
|
+
}
|
|
14
|
+
function GRailRender({ as, override, children, ref, ...rest }) {
|
|
15
|
+
if (override.type === "image") {
|
|
16
|
+
const {
|
|
17
|
+
width,
|
|
18
|
+
height,
|
|
19
|
+
fill,
|
|
20
|
+
src: propSrc,
|
|
21
|
+
alt: propAlt,
|
|
22
|
+
type: _overrideType,
|
|
23
|
+
...imgRest
|
|
24
|
+
} = rest;
|
|
25
|
+
const src = override.src || propSrc || "";
|
|
26
|
+
const alt = override.alt ?? propAlt ?? "";
|
|
27
|
+
if (!src) return null;
|
|
28
|
+
const imgRef = ref;
|
|
29
|
+
if (fill) {
|
|
30
|
+
return /* @__PURE__ */ jsx(NextImage, { ref: imgRef, ...imgRest, src, alt, fill: true });
|
|
31
|
+
}
|
|
32
|
+
const size = intrinsicSize(
|
|
33
|
+
override,
|
|
34
|
+
Number(width) || void 0,
|
|
35
|
+
Number(height) || void 0
|
|
36
|
+
);
|
|
37
|
+
return /* @__PURE__ */ jsx(
|
|
38
|
+
NextImage,
|
|
39
|
+
{
|
|
40
|
+
ref: imgRef,
|
|
41
|
+
...imgRest,
|
|
42
|
+
src,
|
|
43
|
+
alt,
|
|
44
|
+
width: size.width,
|
|
45
|
+
height: size.height
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
if (override.type === "link") {
|
|
50
|
+
const { href: propHref, type: _overrideType, ...linkRest } = rest;
|
|
51
|
+
return /* @__PURE__ */ jsx(
|
|
52
|
+
NextLink,
|
|
53
|
+
{
|
|
54
|
+
ref,
|
|
55
|
+
...linkRest,
|
|
56
|
+
href: override.href || propHref || "#",
|
|
57
|
+
children: override.text || children
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
const Component = as || "span";
|
|
62
|
+
return /* @__PURE__ */ jsx(Component, { ref, ...rest, children: override.text || children });
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
GRailRender
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/react/GRail/render.tsx"],"sourcesContent":["import React from \"react\";\nimport NextImage from \"next/image\";\nimport NextLink from \"next/link\";\nimport type { HTMLKeys, OverrideValue } from \"../../core/types\";\n\ninterface RenderProps {\n as?: HTMLKeys;\n override: OverrideValue;\n children?: React.ReactNode;\n /** Forwarded to the rendered element so the editor can measure it. */\n ref?: React.Ref<HTMLElement>;\n [key: string]: unknown;\n}\n\n/**\n * The developer's width/height set the layout; the image's own proportions set\n * the shape. Keeping the developer's width and deriving height from the stored\n * natural size means a differently shaped replacement neither breaks the\n * layout nor gets stretched.\n */\nfunction intrinsicSize(override: OverrideValue, width?: number, height?: number) {\n const { width: nw, height: nh } = override;\n if (nw && nh) {\n if (width) return { width, height: Math.round((width * nh) / nw) };\n if (height) return { width: Math.round((height * nw) / nh), height };\n return { width: nw, height: nh };\n }\n return { width: width || 500, height: height || 500 };\n}\n\n/**\n * Renders a resolved value as plain markup. Shared by the anonymous SSR path\n * and the editor, so what an editor sees matches what ships.\n *\n * Resolved props are applied *after* the spread. The developer's original\n * src/href are still in `rest`, and spreading them last silently masked every\n * saved image and link override.\n */\nexport function GRailRender({ as, override, children, ref, ...rest }: RenderProps) {\n if (override.type === \"image\") {\n const {\n width,\n height,\n fill,\n src: propSrc,\n alt: propAlt,\n type: _overrideType,\n ...imgRest\n } = rest as Record<string, any>;\n const src = override.src || propSrc || \"\";\n const alt = override.alt ?? propAlt ?? \"\";\n if (!src) return null;\n\n const imgRef = ref as React.Ref<HTMLImageElement>;\n if (fill) {\n return <NextImage ref={imgRef} {...imgRest} src={src} alt={alt} fill />;\n }\n\n const size = intrinsicSize(\n override,\n Number(width) || undefined,\n Number(height) || undefined,\n );\n return (\n <NextImage\n ref={imgRef}\n {...imgRest}\n src={src}\n alt={alt}\n width={size.width}\n height={size.height}\n />\n );\n }\n\n if (override.type === \"link\") {\n const { href: propHref, type: _overrideType, ...linkRest } =\n rest as Record<string, any>;\n return (\n <NextLink\n ref={ref as React.Ref<HTMLAnchorElement>}\n {...linkRest}\n href={override.href || propHref || \"#\"}\n >\n {override.text || children}\n </NextLink>\n );\n }\n\n const Component = (as || \"span\") as React.ElementType;\n return (\n <Component ref={ref} {...rest}>\n {override.text || children}\n </Component>\n );\n}\n"],"mappings":"AAuDa;AAvDb,OAAO,WAAW;AAClB,OAAO,eAAe;AACtB,OAAO,cAAc;AAkBrB,SAAS,cAAc,UAAyB,OAAgB,QAAiB;AAC/E,QAAM,EAAE,OAAO,IAAI,QAAQ,GAAG,IAAI;AAClC,MAAI,MAAM,IAAI;AACZ,QAAI,MAAO,QAAO,EAAE,OAAO,QAAQ,KAAK,MAAO,QAAQ,KAAM,EAAE,EAAE;AACjE,QAAI,OAAQ,QAAO,EAAE,OAAO,KAAK,MAAO,SAAS,KAAM,EAAE,GAAG,OAAO;AACnE,WAAO,EAAE,OAAO,IAAI,QAAQ,GAAG;AAAA,EACjC;AACA,SAAO,EAAE,OAAO,SAAS,KAAK,QAAQ,UAAU,IAAI;AACtD;AAUO,SAAS,YAAY,EAAE,IAAI,UAAU,UAAU,KAAK,GAAG,KAAK,GAAgB;AACjF,MAAI,SAAS,SAAS,SAAS;AAC7B,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA,KAAK;AAAA,MACL,KAAK;AAAA,MACL,MAAM;AAAA,MACN,GAAG;AAAA,IACL,IAAI;AACJ,UAAM,MAAM,SAAS,OAAO,WAAW;AACvC,UAAM,MAAM,SAAS,OAAO,WAAW;AACvC,QAAI,CAAC,IAAK,QAAO;AAEjB,UAAM,SAAS;AACf,QAAI,MAAM;AACR,aAAO,oBAAC,aAAU,KAAK,QAAS,GAAG,SAAS,KAAU,KAAU,MAAI,MAAC;AAAA,IACvE;AAEA,UAAM,OAAO;AAAA,MACX;AAAA,MACA,OAAO,KAAK,KAAK;AAAA,MACjB,OAAO,MAAM,KAAK;AAAA,IACpB;AACA,WACE;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACJ,GAAG;AAAA,QACJ;AAAA,QACA;AAAA,QACA,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA;AAAA,IACf;AAAA,EAEJ;AAEA,MAAI,SAAS,SAAS,QAAQ;AAC5B,UAAM,EAAE,MAAM,UAAU,MAAM,eAAe,GAAG,SAAS,IACvD;AACF,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACC,GAAG;AAAA,QACJ,MAAM,SAAS,QAAQ,YAAY;AAAA,QAElC,mBAAS,QAAQ;AAAA;AAAA,IACpB;AAAA,EAEJ;AAEA,QAAM,YAAa,MAAM;AACzB,SACE,oBAAC,aAAU,KAAW,GAAG,MACtB,mBAAS,QAAQ,UACpB;AAEJ;","names":[]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
declare const CheckIcon: () => react_jsx_runtime.JSX.Element;
|
|
4
|
+
declare const CloseIcon: () => react_jsx_runtime.JSX.Element;
|
|
5
|
+
declare const PeopleIcon: () => react_jsx_runtime.JSX.Element;
|
|
6
|
+
declare const PlusIcon: () => react_jsx_runtime.JSX.Element;
|
|
7
|
+
declare const TrashIcon: () => react_jsx_runtime.JSX.Element;
|
|
8
|
+
declare const ArrowUpIcon: () => react_jsx_runtime.JSX.Element;
|
|
9
|
+
declare const ArrowDownIcon: () => react_jsx_runtime.JSX.Element;
|
|
10
|
+
declare const LogOutIcon: () => react_jsx_runtime.JSX.Element;
|
|
11
|
+
declare const SpinnerIcon: () => react_jsx_runtime.JSX.Element;
|
|
12
|
+
|
|
13
|
+
export { ArrowDownIcon, ArrowUpIcon, CheckIcon, CloseIcon, LogOutIcon, PeopleIcon, PlusIcon, SpinnerIcon, TrashIcon };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
const base = {
|
|
4
|
+
width: 14,
|
|
5
|
+
height: 14,
|
|
6
|
+
viewBox: "0 0 24 24",
|
|
7
|
+
fill: "none",
|
|
8
|
+
stroke: "currentColor",
|
|
9
|
+
strokeWidth: 3,
|
|
10
|
+
strokeLinecap: "round",
|
|
11
|
+
strokeLinejoin: "round"
|
|
12
|
+
};
|
|
13
|
+
const CheckIcon = () => /* @__PURE__ */ jsx("svg", { ...base, "aria-hidden": "true", children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" }) });
|
|
14
|
+
const CloseIcon = () => /* @__PURE__ */ jsxs("svg", { ...base, "aria-hidden": "true", children: [
|
|
15
|
+
/* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
16
|
+
/* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
17
|
+
] });
|
|
18
|
+
const PeopleIcon = () => /* @__PURE__ */ jsxs("svg", { ...base, strokeWidth: 2, "aria-hidden": "true", children: [
|
|
19
|
+
/* @__PURE__ */ jsx("path", { d: "M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" }),
|
|
20
|
+
/* @__PURE__ */ jsx("circle", { cx: "9", cy: "7", r: "4" }),
|
|
21
|
+
/* @__PURE__ */ jsx("path", { d: "M23 21v-2a4 4 0 0 0-3-3.87" }),
|
|
22
|
+
/* @__PURE__ */ jsx("path", { d: "M16 3.13a4 4 0 0 1 0 7.75" })
|
|
23
|
+
] });
|
|
24
|
+
const PlusIcon = () => /* @__PURE__ */ jsxs("svg", { ...base, "aria-hidden": "true", children: [
|
|
25
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
|
|
26
|
+
/* @__PURE__ */ jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" })
|
|
27
|
+
] });
|
|
28
|
+
const TrashIcon = () => /* @__PURE__ */ jsxs("svg", { ...base, strokeWidth: 2, "aria-hidden": "true", children: [
|
|
29
|
+
/* @__PURE__ */ jsx("polyline", { points: "3 6 5 6 21 6" }),
|
|
30
|
+
/* @__PURE__ */ jsx("path", { d: "M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" }),
|
|
31
|
+
/* @__PURE__ */ jsx("path", { d: "M10 11v6M14 11v6M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2" })
|
|
32
|
+
] });
|
|
33
|
+
const ArrowUpIcon = () => /* @__PURE__ */ jsxs("svg", { ...base, "aria-hidden": "true", children: [
|
|
34
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "19", x2: "12", y2: "5" }),
|
|
35
|
+
/* @__PURE__ */ jsx("polyline", { points: "6 11 12 5 18 11" })
|
|
36
|
+
] });
|
|
37
|
+
const ArrowDownIcon = () => /* @__PURE__ */ jsxs("svg", { ...base, "aria-hidden": "true", children: [
|
|
38
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
|
|
39
|
+
/* @__PURE__ */ jsx("polyline", { points: "18 13 12 19 6 13" })
|
|
40
|
+
] });
|
|
41
|
+
const LogOutIcon = () => /* @__PURE__ */ jsxs("svg", { ...base, strokeWidth: 2, "aria-hidden": "true", children: [
|
|
42
|
+
/* @__PURE__ */ jsx("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
|
|
43
|
+
/* @__PURE__ */ jsx("polyline", { points: "16 17 21 12 16 7" }),
|
|
44
|
+
/* @__PURE__ */ jsx("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
|
|
45
|
+
] });
|
|
46
|
+
const SpinnerIcon = () => /* @__PURE__ */ jsx("svg", { ...base, "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.2-8.6", children: /* @__PURE__ */ jsx(
|
|
47
|
+
"animateTransform",
|
|
48
|
+
{
|
|
49
|
+
attributeName: "transform",
|
|
50
|
+
type: "rotate",
|
|
51
|
+
from: "0 12 12",
|
|
52
|
+
to: "360 12 12",
|
|
53
|
+
dur: "0.7s",
|
|
54
|
+
repeatCount: "indefinite"
|
|
55
|
+
}
|
|
56
|
+
) }) });
|
|
57
|
+
export {
|
|
58
|
+
ArrowDownIcon,
|
|
59
|
+
ArrowUpIcon,
|
|
60
|
+
CheckIcon,
|
|
61
|
+
CloseIcon,
|
|
62
|
+
LogOutIcon,
|
|
63
|
+
PeopleIcon,
|
|
64
|
+
PlusIcon,
|
|
65
|
+
SpinnerIcon,
|
|
66
|
+
TrashIcon
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=Icons.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/react/Icons.tsx"],"sourcesContent":["import React from \"react\";\n\n/**\n * Inline SVG rather than an icon package: Guardrail's chrome should not add a\n * runtime dependency to every consuming app.\n */\nconst base = {\n width: 14,\n height: 14,\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: 3,\n strokeLinecap: \"round\" as const,\n strokeLinejoin: \"round\" as const,\n};\n\nexport const CheckIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n);\n\nexport const CloseIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n);\n\nexport const PeopleIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\" />\n <circle cx=\"9\" cy=\"7\" r=\"4\" />\n <path d=\"M23 21v-2a4 4 0 0 0-3-3.87\" />\n <path d=\"M16 3.13a4 4 0 0 1 0 7.75\" />\n </svg>\n);\n\nexport const PlusIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\" />\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\" />\n </svg>\n);\n\nexport const TrashIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <polyline points=\"3 6 5 6 21 6\" />\n <path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\" />\n <path d=\"M10 11v6M14 11v6M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2\" />\n </svg>\n);\n\nexport const ArrowUpIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"5\" />\n <polyline points=\"6 11 12 5 18 11\" />\n </svg>\n);\n\nexport const ArrowDownIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\" />\n <polyline points=\"18 13 12 19 6 13\" />\n </svg>\n);\n\nexport const LogOutIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\" />\n <polyline points=\"16 17 21 12 16 7\" />\n <line x1=\"21\" y1=\"12\" x2=\"9\" y2=\"12\" />\n </svg>\n);\n\nexport const SpinnerIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <path d=\"M21 12a9 9 0 1 1-6.2-8.6\">\n <animateTransform\n attributeName=\"transform\"\n type=\"rotate\"\n from=\"0 12 12\"\n to=\"360 12 12\"\n dur=\"0.7s\"\n repeatCount=\"indefinite\"\n />\n </path>\n </svg>\n);\n"],"mappings":"AAmBI,cAKF,YALE;AAnBJ,OAAO,WAAW;AAMlB,MAAM,OAAO;AAAA,EACX,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,eAAe;AAAA,EACf,gBAAgB;AAClB;AAEO,MAAM,YAAY,MACvB,oBAAC,SAAK,GAAG,MAAM,eAAY,QACzB,8BAAC,cAAS,QAAO,kBAAiB,GACpC;AAGK,MAAM,YAAY,MACvB,qBAAC,SAAK,GAAG,MAAM,eAAY,QACzB;AAAA,sBAAC,UAAK,IAAG,MAAK,IAAG,KAAI,IAAG,KAAI,IAAG,MAAK;AAAA,EACpC,oBAAC,UAAK,IAAG,KAAI,IAAG,KAAI,IAAG,MAAK,IAAG,MAAK;AAAA,GACtC;AAGK,MAAM,aAAa,MACxB,qBAAC,SAAK,GAAG,MAAM,aAAa,GAAG,eAAY,QACzC;AAAA,sBAAC,UAAK,GAAE,6CAA4C;AAAA,EACpD,oBAAC,YAAO,IAAG,KAAI,IAAG,KAAI,GAAE,KAAI;AAAA,EAC5B,oBAAC,UAAK,GAAE,8BAA6B;AAAA,EACrC,oBAAC,UAAK,GAAE,6BAA4B;AAAA,GACtC;AAGK,MAAM,WAAW,MACtB,qBAAC,SAAK,GAAG,MAAM,eAAY,QACzB;AAAA,sBAAC,UAAK,IAAG,MAAK,IAAG,KAAI,IAAG,MAAK,IAAG,MAAK;AAAA,EACrC,oBAAC,UAAK,IAAG,KAAI,IAAG,MAAK,IAAG,MAAK,IAAG,MAAK;AAAA,GACvC;AAGK,MAAM,YAAY,MACvB,qBAAC,SAAK,GAAG,MAAM,aAAa,GAAG,eAAY,QACzC;AAAA,sBAAC,cAAS,QAAO,gBAAe;AAAA,EAChC,oBAAC,UAAK,GAAE,iDAAgD;AAAA,EACxD,oBAAC,UAAK,GAAE,0DAAyD;AAAA,GACnE;AAGK,MAAM,cAAc,MACzB,qBAAC,SAAK,GAAG,MAAM,eAAY,QACzB;AAAA,sBAAC,UAAK,IAAG,MAAK,IAAG,MAAK,IAAG,MAAK,IAAG,KAAI;AAAA,EACrC,oBAAC,cAAS,QAAO,mBAAkB;AAAA,GACrC;AAGK,MAAM,gBAAgB,MAC3B,qBAAC,SAAK,GAAG,MAAM,eAAY,QACzB;AAAA,sBAAC,UAAK,IAAG,MAAK,IAAG,KAAI,IAAG,MAAK,IAAG,MAAK;AAAA,EACrC,oBAAC,cAAS,QAAO,oBAAmB;AAAA,GACtC;AAGK,MAAM,aAAa,MACxB,qBAAC,SAAK,GAAG,MAAM,aAAa,GAAG,eAAY,QACzC;AAAA,sBAAC,UAAK,GAAE,2CAA0C;AAAA,EAClD,oBAAC,cAAS,QAAO,oBAAmB;AAAA,EACpC,oBAAC,UAAK,IAAG,MAAK,IAAG,MAAK,IAAG,KAAI,IAAG,MAAK;AAAA,GACvC;AAGK,MAAM,cAAc,MACzB,oBAAC,SAAK,GAAG,MAAM,eAAY,QACzB,8BAAC,UAAK,GAAE,4BACN;AAAA,EAAC;AAAA;AAAA,IACC,eAAc;AAAA,IACd,MAAK;AAAA,IACL,MAAK;AAAA,IACL,IAAG;AAAA,IACH,KAAI;AAAA,IACJ,aAAY;AAAA;AACd,GACF,GACF;","names":[]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
interface PickedImage {
|
|
4
|
+
src: string;
|
|
5
|
+
width?: number;
|
|
6
|
+
height?: number;
|
|
7
|
+
}
|
|
8
|
+
declare function MediaPicker({ current, onSelect, onClose, }: {
|
|
9
|
+
current?: string;
|
|
10
|
+
onSelect: (image: PickedImage) => void;
|
|
11
|
+
onClose: () => void;
|
|
12
|
+
}): react_jsx_runtime.JSX.Element;
|
|
13
|
+
|
|
14
|
+
export { MediaPicker, type PickedImage };
|