@takuhon/ui 0.10.0 → 0.12.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.
@@ -0,0 +1,18 @@
1
+ .section {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--takuhon-space-3);
5
+ margin-top: var(--takuhon-space-5);
6
+ }
7
+
8
+ .heading {
9
+ margin: 0;
10
+ font-size: var(--takuhon-font-size-xl);
11
+ color: var(--takuhon-color-text);
12
+ }
13
+
14
+ /* The card is a fixed-width SVG; scale it down on narrow viewports. */
15
+ .card svg {
16
+ max-width: 100%;
17
+ height: auto;
18
+ }
@@ -0,0 +1,23 @@
1
+ .uploadRow {
2
+ display: flex;
3
+ flex-wrap: wrap;
4
+ align-items: center;
5
+ gap: var(--takuhon-space-2);
6
+ margin-block-start: var(--takuhon-space-2);
7
+ }
8
+
9
+ .uploadLabel {
10
+ font-family: var(--takuhon-font-family);
11
+ font-size: var(--takuhon-font-size-sm);
12
+ color: var(--takuhon-color-text-muted);
13
+ }
14
+
15
+ .uploadStatus {
16
+ font-size: var(--takuhon-font-size-sm);
17
+ color: var(--takuhon-color-text-muted);
18
+ }
19
+
20
+ .uploadError {
21
+ font-size: var(--takuhon-font-size-sm);
22
+ color: #b42318;
23
+ }
@@ -135,6 +135,47 @@ interface CheckboxFieldProps {
135
135
  */
136
136
  declare function CheckboxField({ label, checked, onChange }: CheckboxFieldProps): React.JSX.Element;
137
137
 
138
+ /** Outcome of an asset upload, returned by {@link UploadAsset}. */
139
+ type AssetUploadResult = {
140
+ status: 'uploaded';
141
+ url: string;
142
+ publicUrl: string;
143
+ } | {
144
+ status: 'error';
145
+ message?: string;
146
+ };
147
+ /**
148
+ * Upload one image file and resolve where it now lives. Supplied by the host's
149
+ * transport layer (it carries the admin token / origin); the UI stays
150
+ * transport-agnostic.
151
+ */
152
+ type UploadAsset = (file: File) => Promise<AssetUploadResult>;
153
+ interface ImageFieldProps {
154
+ label: string;
155
+ /** Current image URL stored in the document. */
156
+ value: string;
157
+ onChange: (url: string) => void;
158
+ errors?: readonly string[];
159
+ hint?: string;
160
+ /**
161
+ * When provided, a file-upload control is rendered alongside the URL input;
162
+ * a successful upload sets the URL to the returned (relative) path. When
163
+ * omitted, the field is URL-only.
164
+ */
165
+ uploadAsset?: UploadAsset;
166
+ }
167
+ /**
168
+ * Avatar / image field: a URL input plus, when {@link ImageFieldProps.uploadAsset}
169
+ * is supplied, a file picker that uploads the chosen image and writes the
170
+ * returned URL back into the field. The relative `url` (not the absolute
171
+ * `publicUrl`) is stored so the reference stays valid across origins.
172
+ *
173
+ * No `<img>` preview is rendered: the admin CSP is `img-src 'self' blob:`, so a
174
+ * preview of an arbitrary external URL would be blocked — the URL input is the
175
+ * portable, CSP-safe surface, and the public preview shows the real image.
176
+ */
177
+ declare function ImageField({ label, value, onChange, errors, hint, uploadAsset, }: ImageFieldProps): React.JSX.Element;
178
+
138
179
  interface LocaleTabsProps {
139
180
  /** Group label, e.g. `"Display name"`. */
140
181
  label: string;
@@ -229,6 +270,8 @@ declare const EN: {
229
270
  readonly 'status.error': "Something went wrong. Please try again.";
230
271
  readonly 'status.imported': "Imported. Review the fields, then save to apply.";
231
272
  readonly 'status.importInvalid': "The imported file is not a valid takuhon document.";
273
+ readonly 'status.uploading': "Uploading…";
274
+ readonly 'status.uploadError': "Upload failed. Please try again.";
232
275
  readonly 'section.profile': "Profile";
233
276
  readonly 'section.about': "About";
234
277
  readonly 'section.links': "Links";
@@ -240,6 +283,7 @@ declare const EN: {
240
283
  readonly 'field.tagline': "Tagline";
241
284
  readonly 'field.bio': "Bio";
242
285
  readonly 'field.avatarUrl': "Avatar URL";
286
+ readonly 'field.avatarUpload': "Upload image";
243
287
  readonly 'field.avatarAlt': "Avatar alternative text";
244
288
  readonly 'field.location.country': "Country";
245
289
  readonly 'field.location.region': "Region";
@@ -283,6 +327,7 @@ declare const EN: {
283
327
  readonly 'empty.projects': "No projects yet.";
284
328
  readonly 'empty.skills': "No skills yet.";
285
329
  readonly 'hint.avatarNoUpload': "Paste an image URL. Uploading image files is not available yet.";
330
+ readonly 'hint.avatarUpload': "Paste an image URL, or upload a JPEG, PNG, WebP, or GIF.";
286
331
  readonly 'hint.month': "Format: YYYY-MM (e.g. 2024-03).";
287
332
  readonly 'hint.country': "ISO 3166-1 alpha-2 code, e.g. US.";
288
333
  readonly 'hint.tags': "Comma-separated.";
@@ -306,13 +351,18 @@ interface ProfileFormProps {
306
351
  locales: readonly LocaleTag[];
307
352
  errors?: FieldErrorIndex;
308
353
  formatLocale?: (locale: LocaleTag) => string;
354
+ /**
355
+ * Upload an avatar image file. When provided, the avatar field offers a file
356
+ * picker; when omitted, it stays URL-only.
357
+ */
358
+ uploadAsset?: UploadAsset;
309
359
  }
310
360
  /**
311
- * Basic profile + About: display name, tagline, bio, avatar URL, and a
312
- * structured location (spec §6.5 / §14.2). Image upload is not wired yet, so
313
- * the avatar is a URL field only.
361
+ * Basic profile + About: display name, tagline, bio, avatar, and a structured
362
+ * location (spec §6.5 / §14.2). The avatar accepts a URL and, when
363
+ * `uploadAsset` is supplied by the host, an uploaded image file.
314
364
  */
315
- declare function ProfileForm({ value, onChange, locales, errors, formatLocale, }: ProfileFormProps): React.JSX.Element;
365
+ declare function ProfileForm({ value, onChange, locales, errors, formatLocale, uploadAsset, }: ProfileFormProps): React.JSX.Element;
316
366
 
317
367
  interface LinksFormProps {
318
368
  value: readonly Link[];
@@ -404,6 +454,12 @@ interface AdminEditorProps {
404
454
  */
405
455
  onImport?: () => Promise<unknown>;
406
456
  formatLocale?: (locale: LocaleTag) => string;
457
+ /**
458
+ * Upload an avatar image file (host-supplied; carries the admin token). When
459
+ * provided, the profile form's avatar field offers a file picker; otherwise
460
+ * the avatar stays URL-only.
461
+ */
462
+ uploadAsset?: UploadAsset;
407
463
  }
408
464
  /**
409
465
  * Top-level admin editor: holds the working draft, switches between the field
@@ -411,6 +467,6 @@ interface AdminEditorProps {
411
467
  * delegating persistence to the host via `onSave` (transport-agnostic). Server
412
468
  * (RFC 7807) and client (`validate`) errors map to the same fields.
413
469
  */
414
- declare function AdminEditor({ initialDocument, onSave, onReload, onExport, onImport, formatLocale, }: AdminEditorProps): React.JSX.Element;
470
+ declare function AdminEditor({ initialDocument, onSave, onReload, onExport, onImport, formatLocale, uploadAsset, }: AdminEditorProps): React.JSX.Element;
415
471
 
416
- export { AdminEditor, type AdminEditorProps, type AdminLabelKey, type AdminSaveOutcome, CareersForm, type CareersFormProps, CheckboxField, type CheckboxFieldProps, Field, type FieldControlProps, type FieldErrorIndex, type FieldErrorLike, type FieldProps, LinksForm, type LinksFormProps, LocaleTabs, type LocaleTabsProps, NO_FIELD_ERRORS, ProfileForm, type ProfileFormProps, ProjectsForm, type ProjectsFormProps, RawJsonEditor, type RawJsonEditorProps, Repeater, type RepeaterProps, SelectField, type SelectFieldProps, type SelectOption, SettingsForm, type SettingsFormProps, SkillsForm, type SkillsFormProps, TextAreaField, type TextAreaFieldProps, TextField, type TextFieldProps, canonicalPointer, collectErrorsUnder, errorsAt, getAdminLabel, hasErrorsUnder, indexErrors, indexValidationErrors };
472
+ export { AdminEditor, type AdminEditorProps, type AdminLabelKey, type AdminSaveOutcome, type AssetUploadResult, CareersForm, type CareersFormProps, CheckboxField, type CheckboxFieldProps, Field, type FieldControlProps, type FieldErrorIndex, type FieldErrorLike, type FieldProps, ImageField, type ImageFieldProps, LinksForm, type LinksFormProps, LocaleTabs, type LocaleTabsProps, NO_FIELD_ERRORS, ProfileForm, type ProfileFormProps, ProjectsForm, type ProjectsFormProps, RawJsonEditor, type RawJsonEditorProps, Repeater, type RepeaterProps, SelectField, type SelectFieldProps, type SelectOption, SettingsForm, type SettingsFormProps, SkillsForm, type SkillsFormProps, TextAreaField, type TextAreaFieldProps, TextField, type TextFieldProps, type UploadAsset, canonicalPointer, collectErrorsUnder, errorsAt, getAdminLabel, hasErrorsUnder, indexErrors, indexValidationErrors };