@vit-foundation/ui 0.2.1 → 0.5.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 CHANGED
@@ -1,116 +1,70 @@
1
1
  # @vit-foundation/ui
2
2
 
3
3
  Fundació VIT's shared Svelte 5 component library: the components behind
4
- [fundaciovit.org](https://fundaciovit.org), decoupled from any one app, with an
5
- optional **edit mode** that lets a CMS-shaped consumer make the rendered
4
+ [fundaciovit.org](https://fundaciovit.org), decoupled from any one app, with
5
+ an optional **edit mode** that lets a CMS-shaped consumer make the rendered
6
6
  content editable in place.
7
7
 
8
8
  ```sh
9
9
  npm install @vit-foundation/ui
10
10
  ```
11
11
 
12
- Requires `svelte` ^5.0.0 as a peer.
13
-
14
- ## Quick start
15
-
16
- ```svelte
17
- <script>
18
- import { WeeklieCard, PageShell } from '@vit-foundation/ui';
19
- import '@vit-foundation/ui/tokens.css'; // design tokens (CSS custom properties)
20
- import '@vit-foundation/ui/base.css'; // utility classes + edit affordance
21
- </script>
22
-
23
- <PageShell title="Weeklies">
24
- <WeeklieCard weekly={data.weekly} />
25
- </PageShell>
26
- ```
27
-
28
- Every component renders standalone with Catalan copy and identity hrefs.
29
- Subpath imports are available when you want a narrower graph:
30
- `@vit-foundation/ui/ui`, `/layout`, `/weeklies`, `/projects`, `/team`,
31
- `/timeline`, `/jobs`, `/edit`, `/config`.
32
-
33
- ## Wiring a host app: `UiProvider`
34
-
35
- Strings, href resolution, the active locale, and the current URL come from a
36
- `UiConfig` context. Mount it once, in the root layout. The foundation website
37
- wires it to Paraglide and SvelteKit like this:
12
+ Requires `svelte` ^5.0.0 as a peer. Import the two stylesheets once:
38
13
 
39
14
  ```svelte
40
15
  <script>
41
- import { UiProvider } from '@vit-foundation/ui';
42
- import { page } from '$app/state';
43
- import { m } from '$lib/paraglide/messages';
44
- import { getLocale, locales, deLocalizeUrl } from '$lib/paraglide/runtime';
16
+ import '@vit-foundation/ui/tokens.css';
17
+ import '@vit-foundation/ui/base.css';
45
18
  </script>
46
-
47
- <UiProvider
48
- config={{
49
- href: (path, options) => localizeHref(path, options),
50
- locale: () => getLocale(),
51
- locales,
52
- url: () => page.url,
53
- canonicalPathname: (url) => deLocalizeUrl(url).pathname,
54
- siteName: 'ViT',
55
- messages: m
56
- }}
57
- >
58
- {@render children()}
59
- </UiProvider>
60
- ```
61
-
62
- `locale`, `url`, and `messages` reads are lazy, so reactive sources flow
63
- through: a locale switch re-renders every consumer without a remount.
64
- `messages` is a typed structural subset of a Paraglide `m` module — pass it
65
- wholesale. Keys consumed only through this package still count as used;
66
- don't prune them from your message catalogue.
67
-
68
- ## Edit mode
69
-
70
- Components render plain, already-localized strings. To make one editable,
71
- pass an _edit descriptor_ naming what the string is — and install an
72
- `EditAdapter` that knows how to persist it. No adapter (or `isEditing`
73
- off) means every descriptor is inert and the render is byte-identical to
74
- the read-only one. **The package never talks to a database.**
75
-
76
- ```svelte
77
- <script>
78
- import { setEditAdapter, entityEdit, WeeklieCard } from '@vit-foundation/ui';
79
-
80
- let editing = $state(false);
81
-
82
- setEditAdapter({
83
- get isEditing() {
84
- return editing;
85
- },
86
- // Merge { [descriptor.locale]: value } into the localized column.
87
- // Reject when persistence fails — the editor keeps the draft and
88
- // shows the error state. Never let a save empty the canonical `ca`.
89
- save: (descriptor, value) => saveContent(descriptor, value)
90
- });
91
-
92
- const edit = entityEdit('weeklies', weekly.id, 'ca');
93
- </script>
94
-
95
- <WeeklieCard {weekly} edit={{ title: edit('title'), excerpt: edit('excerpt') }} />
96
- ```
97
-
98
- Editing UX: click into an outlined text, type, and blur or press Enter to
99
- save (Cmd/Ctrl+Enter for multiline fields, Escape reverts). `RichText`
100
- opens a source editor over the block mini-format with a live preview.
101
- Wrap your own render sites with the `Editable` primitive:
102
-
103
- ```svelte
104
- <Editable edit={pageCopyEdit('home', 'hero_title', 'ca')} value={copy.hero_title}>
105
- {#snippet children(text, attrs)}
106
- <h1 {...attrs}>{text}</h1>
107
- {/snippet}
108
- </Editable>
109
19
  ```
110
20
 
111
- The `content` types (`LocalizedText`, `ContentRef`, …) mirror the
112
- foundation's content model: localized text is a per-locale record whose
113
- Catalan value is canonical and required.
21
+ ## The library at a glance
22
+
23
+ Everything exports flat from the root, and again grouped by role:
24
+
25
+ | Entry point | What lives there |
26
+ | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
27
+ | [`/primitives`](./docs/components/primitives.md) | Button, GhostButton, Link, CardMedia, CardTitle, CopyIntro, DateText, DecorShapes, FilterChips, Pagination, RichText, SearchInput, ShareRow |
28
+ | [`/forms`](./docs/forms.md) | Field, FormFeedback, FormErrorFeedback, FormResultSlot, Honeypot, LocaleField, NewsletterIntentField — plus the remote-form seam: structural types, transport constants, field bounds |
29
+ | [`/chrome`](./docs/components/chrome.md) | PageShell, Nav, Footer |
30
+ | [`/content`](./docs/components/content.md) | WeeklieCard, ProjectCard, Timeline, TimelineMilestone, TeamMemberCard, CollaboratorList, JobList, SortSelect — plus the data shapes and helpers |
31
+ | [`/community`](./docs/components/community.md) | AuthPageShell, LoginForm, SignupForm, GoogleAuthForm, AccountPanel, NewsletterSignup, CommentSection, ReactionBar, ContactForm |
32
+ | [`/edit`](./docs/edit-mode.md) | Editable, setEditAdapter/getEditAdapter, descriptors and helpers, LocalizedText |
33
+ | [`/config`](./docs/getting-started.md#wiring-an-app-uiprovider) | UiProvider, UiConfig, the locale set, the default Catalan messages |
34
+ | `/testing` | createRemoteFormMock — the remote-form stand-in for stories and tests |
35
+
36
+ Every component renders standalone (Catalan copy, identity hrefs); an app
37
+ integrates its i18n and router through one `UiProvider` in the root layout.
38
+
39
+ ## Documentation
40
+
41
+ - **[Getting started](./docs/getting-started.md)** — install, styles, entry
42
+ points, wiring `UiProvider` (with the foundation website's Paraglide +
43
+ SvelteKit example)
44
+ - **[Edit mode](./docs/edit-mode.md)** — descriptors, the `EditAdapter`
45
+ contract, the `Editable` primitive, the editing UX, the save semantics
46
+ - **[Forms](./docs/forms.md)** — the remote-form seam: passing preflighted
47
+ forms in, keyed factories, the shared constants, the form building blocks
48
+ - **[Theming](./docs/theming.md)** — the design tokens and the base utilities
49
+ - **Component reference** — [primitives](./docs/components/primitives.md) ·
50
+ [chrome](./docs/components/chrome.md) ·
51
+ [content](./docs/components/content.md) ·
52
+ [community](./docs/components/community.md)
53
+ - **Storybook** — `npm run storybook`: every component has a story;
54
+ `Edit mode/EditMode` demos the whole editing loop against an in-memory
55
+ adapter
56
+
57
+ ## Two design rules worth knowing before anything else
58
+
59
+ 1. **The package never talks to a backend.** Content arrives as props; edits
60
+ leave through an injected `EditAdapter`; remote forms are passed in
61
+ preflighted by the host against its own schemas. An app that injects
62
+ nothing gets a read-only render, byte-identical to a build with no edit
63
+ mode at all.
64
+ 2. **Shared constants have one owner.** The honeypot field name, the
65
+ newsletter-intent encoding, and the field bounds export from `./forms`;
66
+ host apps import them into their schemas so markup and server envelope
67
+ can never drift apart.
114
68
 
115
69
  ## Development
116
70
 
@@ -122,8 +76,12 @@ npm run check && npm run lint && npm run test:unit -- --run
122
76
  npm run build # svelte-package + publint
123
77
  ```
124
78
 
125
- Stories live in `src/lib/components/stories/<group>/`, component tests in
126
- `src/lib/components/tests/<group>/`, mirroring the component tree.
79
+ Internally, components live in `src/lib/components/<website-group>/` with
80
+ stories in `components/stories/<group>/` and tests in
81
+ `components/tests/<group>/` — the layout they had in fndvit-website, kept
82
+ diffable against history. The public structure is the semantic entry points
83
+ above, assembled in `src/lib/{primitives,chrome,content-components,community}.ts`
84
+ and `src/lib/forms/index.ts`.
127
85
 
128
86
  Releases: bump with `npm version`, push the `v*` tag, and the release
129
- workflow publishes to npm.
87
+ workflow publishes to npm (requires the `NPM_TOKEN` secret).
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Site chrome: the shell every page renders through, and the header/footer
3
+ * pair. Nav and Footer take the same SiteLink[] so the two can never drift.
4
+ */
5
+ export { default as Footer } from './components/layout/Footer.svelte';
6
+ export { default as Nav } from './components/layout/Nav.svelte';
7
+ export { default as PageShell } from './components/layout/PageShell.svelte';
package/dist/chrome.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Site chrome: the shell every page renders through, and the header/footer
3
+ * pair. Nav and Footer take the same SiteLink[] so the two can never drift.
4
+ */
5
+ export { default as Footer } from './components/layout/Footer.svelte';
6
+ export { default as Nav } from './components/layout/Nav.svelte';
7
+ export { default as PageShell } from './components/layout/PageShell.svelte';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The server-flow components: auth, account, newsletter, comments, reactions
3
+ * and contact. Every one takes its remote form(s) as required props — the
4
+ * host app owns schemas and preflight; see ./forms for the seam's types.
5
+ */
6
+ export * from './components/account/index.js';
7
+ export * from './components/auth/index.js';
8
+ export * from './components/contact/index.js';
9
+ export { default as CommentSection } from './components/weeklies/CommentSection.svelte';
10
+ export type { CommentFormInstance } from './components/weeklies/CommentSection.svelte';
11
+ export { default as ReactionBar } from './components/weeklies/ReactionBar.svelte';
12
+ export type { CommentReactionChip, ReactionBarForms, WeeklyReactionChip } from './components/weeklies/ReactionBar.svelte';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The server-flow components: auth, account, newsletter, comments, reactions
3
+ * and contact. Every one takes its remote form(s) as required props — the
4
+ * host app owns schemas and preflight; see ./forms for the seam's types.
5
+ */
6
+ export * from './components/account/index.js';
7
+ export * from './components/auth/index.js';
8
+ export * from './components/contact/index.js';
9
+ export { default as CommentSection } from './components/weeklies/CommentSection.svelte';
10
+ export { default as ReactionBar } from './components/weeklies/ReactionBar.svelte';
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { getUiConfig } from '../../config/context.js';
3
+ import Editable from '../../edit/Editable.svelte';
3
4
  import { withNewsletterIntent } from '../../forms/transport.js';
4
5
  import type { NewsletterToggleFormInstance } from './AccountPanel.svelte';
5
6
  import Button from '../ui/Button.svelte';
@@ -27,19 +28,33 @@
27
28
 
28
29
  <section class="newsletter" aria-labelledby="newsletter-title">
29
30
  <div class="inner band">
30
- <h2 id="newsletter-title">{msg.newsletter_title()}</h2>
31
- <p>{msg.newsletter_intro()}</p>
31
+ <Editable edit={config.messageEdit?.('newsletter_title')} value={msg.newsletter_title()}>
32
+ {#snippet children(text, attrs)}<h2 id="newsletter-title" {...attrs}>{text}</h2>{/snippet}
33
+ </Editable>
34
+ <Editable edit={config.messageEdit?.('newsletter_intro')} value={msg.newsletter_intro()}>
35
+ {#snippet children(text, attrs)}<p {...attrs}>{text}</p>{/snippet}
36
+ </Editable>
32
37
 
33
38
  {#if account === null}
34
39
  <p class="prompt">
35
- {msg.newsletter_promptLoggedOut()}
40
+ <Editable
41
+ edit={config.messageEdit?.('newsletter_promptLoggedOut')}
42
+ value={msg.newsletter_promptLoggedOut()}
43
+ >
44
+ {#snippet children(text, attrs)}<span {...attrs}>{text}</span>{/snippet}
45
+ </Editable>
36
46
  <Link href={withNewsletterIntent('/signup')}>{msg.comments_signupLink()}</Link>
37
47
  ·
38
48
  <Link href={withNewsletterIntent('/login')}>{msg.comments_loginLink()}</Link>
39
49
  </p>
40
50
  {:else if account.newsletterSubscribed}
41
51
  <p class="prompt">
42
- {msg.newsletter_subscribedNote()}
52
+ <Editable
53
+ edit={config.messageEdit?.('newsletter_subscribedNote')}
54
+ value={msg.newsletter_subscribedNote()}
55
+ >
56
+ {#snippet children(text, attrs)}<span {...attrs}>{text}</span>{/snippet}
57
+ </Editable>
43
58
  <Link href="/account">{msg.account_navLabel()}</Link>
44
59
  </p>
45
60
  {:else}
@@ -30,8 +30,12 @@
30
30
  </script>
31
31
 
32
32
  {#if jobs.length === 0}
33
- <p class="empty">{msg.jobs_empty()}</p>
34
- <p class="nudge">{msg.jobs_newsletterNudge()}</p>
33
+ <Editable edit={config.messageEdit?.('jobs_empty')} value={msg.jobs_empty()}>
34
+ {#snippet children(text, attrs)}<p class="empty" {...attrs}>{text}</p>{/snippet}
35
+ </Editable>
36
+ <Editable edit={config.messageEdit?.('jobs_newsletterNudge')} value={msg.jobs_newsletterNudge()}>
37
+ {#snippet children(text, attrs)}<p class="nudge" {...attrs}>{text}</p>{/snippet}
38
+ </Editable>
35
39
  {:else}
36
40
  <ul>
37
41
  {#each jobs as job (job.slug)}
@@ -49,7 +53,9 @@
49
53
  </li>
50
54
  {/each}
51
55
  </ul>
52
- <p class="nudge">{msg.jobs_newsletterNudge()}</p>
56
+ <Editable edit={config.messageEdit?.('jobs_newsletterNudge')} value={msg.jobs_newsletterNudge()}>
57
+ {#snippet children(text, attrs)}<p class="nudge" {...attrs}>{text}</p>{/snippet}
58
+ </Editable>
53
59
  {/if}
54
60
 
55
61
  <style>
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { getUiConfig } from '../../config/context.js';
3
3
  import type { SiteLink } from '../../config/types.js';
4
+ import Editable from '../../edit/Editable.svelte';
4
5
  import Link from '../ui/Link.svelte';
5
6
 
6
7
  /**
@@ -29,7 +30,15 @@
29
30
  <li><Link href={link.href}>{link.label}</Link></li>
30
31
  {/each}
31
32
  </ul>
32
- <p class="rights">© {year} {config.messages.footer_rights()}</p>
33
+ <p class="rights">
34
+ © {year}
35
+ <Editable
36
+ edit={config.messageEdit?.('footer_rights')}
37
+ value={config.messages.footer_rights()}
38
+ >
39
+ {#snippet children(text, attrs)}<span {...attrs}>{text}</span>{/snippet}
40
+ </Editable>
41
+ </p>
33
42
  </div>
34
43
  </footer>
35
44
 
@@ -10,6 +10,7 @@
10
10
 
11
11
  <script lang="ts">
12
12
  import { getUiConfig } from '../../config/context.js';
13
+ import Editable from '../../edit/Editable.svelte';
13
14
  import { COMMENT_BODY } from '../../forms/constraints.js';
14
15
  import type { CommentData, CommentThreadData } from '../../content/types.js';
15
16
  import Button from '../ui/Button.svelte';
@@ -67,10 +68,16 @@
67
68
  {/snippet}
68
69
 
69
70
  <section aria-labelledby="comments-heading">
70
- <h2 class="subsection-heading" id="comments-heading">{msg.comments_title()}</h2>
71
+ <Editable edit={config.messageEdit?.('comments_title')} value={msg.comments_title()}>
72
+ {#snippet children(text, attrs)}
73
+ <h2 class="subsection-heading" id="comments-heading" {...attrs}>{text}</h2>
74
+ {/snippet}
75
+ </Editable>
71
76
 
72
77
  {#if comments.length === 0}
73
- <p class="empty">{msg.comments_empty()}</p>
78
+ <Editable edit={config.messageEdit?.('comments_empty')} value={msg.comments_empty()}>
79
+ {#snippet children(text, attrs)}<p class="empty" {...attrs}>{text}</p>{/snippet}
80
+ </Editable>
74
81
  {:else}
75
82
  <ul class="list">
76
83
  {#each comments as thread (thread.id)}
@@ -148,7 +155,12 @@
148
155
  </form>
149
156
  {:else}
150
157
  <p class="login-prompt">
151
- {msg.comments_loginPrompt()}
158
+ <Editable
159
+ edit={config.messageEdit?.('comments_loginPrompt')}
160
+ value={msg.comments_loginPrompt()}
161
+ >
162
+ {#snippet children(text, attrs)}<span {...attrs}>{text}</span>{/snippet}
163
+ </Editable>
152
164
  <Link href="/login">{msg.comments_loginLink()}</Link>
153
165
  ·
154
166
  <Link href="/signup">{msg.comments_signupLink()}</Link>
@@ -37,7 +37,10 @@ export function setUiConfig(input) {
37
37
  },
38
38
  get messages() {
39
39
  return input().messages ?? defaultMessages;
40
- }
40
+ },
41
+ // Undefined means inert: components render their message strings as
42
+ // plain text, which is every app but the CMS.
43
+ messageEdit: (key) => input().messageEdit?.(key)
41
44
  };
42
45
  return setContext(KEY, resolved);
43
46
  }
@@ -1,3 +1,4 @@
1
+ import type { EditDescriptor } from '../edit/types.js';
1
2
  /**
2
3
  * The site's locales and their canonical member. Fixed rather than generic:
3
4
  * `ca` is load-bearing — it is the required key of every localized column
@@ -156,6 +157,14 @@ export interface UiConfig {
156
157
  /** Appended to document titles and shown as the wordmark. */
157
158
  siteName: string;
158
159
  messages: UiMessages;
160
+ /**
161
+ * Edit descriptor for one of THIS config's message strings, by catalog
162
+ * key — the interface-wording half of edit mode. Optional and undefined by
163
+ * default, so a read-only app (and every story/test) renders the messages
164
+ * as plain text. A CMS-shaped host supplies it alongside an EditAdapter;
165
+ * components offer only their parameterless, plain-text message sites.
166
+ */
167
+ messageEdit?: (key: string) => EditDescriptor | undefined;
159
168
  }
160
169
  /** What an app hands UiProvider: any subset; the rest keeps package defaults. */
161
170
  export interface UiConfigInput {
@@ -166,4 +175,5 @@ export interface UiConfigInput {
166
175
  canonicalPathname?: UiConfig['canonicalPathname'];
167
176
  siteName?: string;
168
177
  messages?: UiMessages;
178
+ messageEdit?: UiConfig['messageEdit'];
169
179
  }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Content renderers: the components that present database content — cards,
3
+ * the timeline, listings. All prop-driven, all edit-mode capable, plus the
4
+ * structural data shapes they consume.
5
+ */
6
+ export { default as CollaboratorList } from './components/team/CollaboratorList.svelte';
7
+ export { default as JobList } from './components/jobs/JobList.svelte';
8
+ export type { JobEditMap } from './components/jobs/JobList.svelte';
9
+ export { default as ProjectCard } from './components/projects/ProjectCard.svelte';
10
+ export type { ProjectEditMap } from './components/projects/ProjectCard.svelte';
11
+ export { default as SortSelect } from './components/weeklies/SortSelect.svelte';
12
+ export { default as TeamMemberCard } from './components/team/TeamMemberCard.svelte';
13
+ export type { TeamMemberEditMap } from './components/team/TeamMemberCard.svelte';
14
+ export { default as Timeline } from './components/timeline/Timeline.svelte';
15
+ export { default as TimelineMilestone } from './components/timeline/TimelineMilestone.svelte';
16
+ export type { MilestoneEditMap } from './components/timeline/TimelineMilestone.svelte';
17
+ export { default as WeeklieCard } from './components/weeklies/WeeklieCard.svelte';
18
+ export type { WeeklyEditMap } from './components/weeklies/WeeklieCard.svelte';
19
+ export * from './content/index.js';
20
+ export { MILESTONE_CATEGORY_COLOR, milestoneCategoryLabel } from './utils/milestones.js';
21
+ export { contactCategoryLabel } from './utils/contact.js';
22
+ export { formatDate, yearOf } from './utils/dates.js';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Content renderers: the components that present database content — cards,
3
+ * the timeline, listings. All prop-driven, all edit-mode capable, plus the
4
+ * structural data shapes they consume.
5
+ */
6
+ export { default as CollaboratorList } from './components/team/CollaboratorList.svelte';
7
+ export { default as JobList } from './components/jobs/JobList.svelte';
8
+ export { default as ProjectCard } from './components/projects/ProjectCard.svelte';
9
+ export { default as SortSelect } from './components/weeklies/SortSelect.svelte';
10
+ export { default as TeamMemberCard } from './components/team/TeamMemberCard.svelte';
11
+ export { default as Timeline } from './components/timeline/Timeline.svelte';
12
+ export { default as TimelineMilestone } from './components/timeline/TimelineMilestone.svelte';
13
+ export { default as WeeklieCard } from './components/weeklies/WeeklieCard.svelte';
14
+ export * from './content/index.js';
15
+ export { MILESTONE_CATEGORY_COLOR, milestoneCategoryLabel } from './utils/milestones.js';
16
+ export { contactCategoryLabel } from './utils/contact.js';
17
+ export { formatDate, yearOf } from './utils/dates.js';
@@ -1,5 +1,14 @@
1
1
  import type { Locale } from '../config/types.js';
2
2
  import type { EditableEntity, EditDescriptor } from './types.js';
3
+ /**
4
+ * Descriptor for one interface-wording message (a Paraglide catalog key).
5
+ * Only parameterless messages are sensibly editable in place — editing the
6
+ * RENDERED text of a parameterized one would overwrite its template.
7
+ */
8
+ export declare function chromeEdit(key: string, locale: Locale, options?: {
9
+ format?: EditDescriptor['format'];
10
+ label?: string;
11
+ }): EditDescriptor;
3
12
  /** Descriptor for one page-copy block (`page_content` row). */
4
13
  export declare function pageCopyEdit(page: string, sectionKey: string, locale: Locale, options?: {
5
14
  format?: EditDescriptor['format'];
@@ -1,3 +1,11 @@
1
+ /**
2
+ * Descriptor for one interface-wording message (a Paraglide catalog key).
3
+ * Only parameterless messages are sensibly editable in place — editing the
4
+ * RENDERED text of a parameterized one would overwrite its template.
5
+ */
6
+ export function chromeEdit(key, locale, options) {
7
+ return { ref: { kind: 'chrome', key }, locale, ...options };
8
+ }
1
9
  /** Descriptor for one page-copy block (`page_content` row). */
2
10
  export function pageCopyEdit(page, sectionKey, locale, options) {
3
11
  return { ref: { kind: 'page-copy', page, sectionKey }, locale, ...options };
@@ -1,5 +1,5 @@
1
1
  export { getEditAdapter, setEditAdapter } from './context.js';
2
2
  export { default as Editable } from './Editable.svelte';
3
- export { entityEdit, pageCopyEdit } from './helpers.js';
3
+ export { chromeEdit, entityEdit, pageCopyEdit } from './helpers.js';
4
4
  export { localize } from './types.js';
5
5
  export type { ContentRef, EditableEntity, EditAdapter, EditDescriptor, LocalizedText } from './types.js';
@@ -1,4 +1,4 @@
1
1
  export { getEditAdapter, setEditAdapter } from './context.js';
2
2
  export { default as Editable } from './Editable.svelte';
3
- export { entityEdit, pageCopyEdit } from './helpers.js';
3
+ export { chromeEdit, entityEdit, pageCopyEdit } from './helpers.js';
4
4
  export { localize } from './types.js';
@@ -17,8 +17,12 @@ export declare function localize(value: LocalizedText, locale: Locale): string;
17
17
  */
18
18
  export type EditableEntity = 'weeklies' | 'projects' | 'milestones' | 'team_members' | 'job_openings';
19
19
  /**
20
- * The identity of one editable string in the content model: either a page
21
- * copy block (`page_content` row) or one localized field of a domain entity.
20
+ * The identity of one editable string in the content model: a page copy
21
+ * block (`page_content` row), one localized field of a domain entity, or one
22
+ * interface-wording message (`chrome`) — a Paraglide catalog key. What a
23
+ * chrome save MEANS is the host's decision, like every other ref: the
24
+ * foundation CMS writes the website's `ui_messages` table, from which the
25
+ * catalogs are regenerated at deploy.
22
26
  */
23
27
  export type ContentRef = {
24
28
  kind: 'page-copy';
@@ -29,6 +33,9 @@ export type ContentRef = {
29
33
  entity: EditableEntity;
30
34
  id: string | number;
31
35
  field: string;
36
+ } | {
37
+ kind: 'chrome';
38
+ key: string;
32
39
  };
33
40
  /** What a component hands the adapter alongside the new value. */
34
41
  export interface EditDescriptor {
@@ -1,3 +1,10 @@
1
+ export { default as Field } from '../components/ui/Field.svelte';
2
+ export { default as FormErrorFeedback } from '../components/ui/FormErrorFeedback.svelte';
3
+ export { default as FormFeedback } from '../components/ui/FormFeedback.svelte';
4
+ export { default as FormResultSlot } from '../components/ui/FormResultSlot.svelte';
5
+ export { default as Honeypot } from '../components/ui/Honeypot.svelte';
6
+ export { default as LocaleField } from '../components/ui/LocaleField.svelte';
7
+ export { default as NewsletterIntentField } from '../components/ui/NewsletterIntentField.svelte';
1
8
  export { COMMENT_BODY, CONTACT_MESSAGE, CONTACT_NAME, DISPLAY_NAME, EMAIL, LOGIN_PASSWORD, PASSWORD } from './constraints.js';
2
9
  export { hasNewsletterIntent, HONEYPOT_FIELD, isNewsletterIntent, NEWSLETTER_INTENT_PARAM, NEWSLETTER_INTENT_VALUE, withNewsletterIntent } from './transport.js';
3
10
  export type { FormFail, FormFieldIssue, FormResultLike, FormResultOf, KeyedRemoteForms, RemoteField, RemoteFormAttributes, RemoteFormInstance } from './types.js';
@@ -1,2 +1,9 @@
1
+ export { default as Field } from '../components/ui/Field.svelte';
2
+ export { default as FormErrorFeedback } from '../components/ui/FormErrorFeedback.svelte';
3
+ export { default as FormFeedback } from '../components/ui/FormFeedback.svelte';
4
+ export { default as FormResultSlot } from '../components/ui/FormResultSlot.svelte';
5
+ export { default as Honeypot } from '../components/ui/Honeypot.svelte';
6
+ export { default as LocaleField } from '../components/ui/LocaleField.svelte';
7
+ export { default as NewsletterIntentField } from '../components/ui/NewsletterIntentField.svelte';
1
8
  export { COMMENT_BODY, CONTACT_MESSAGE, CONTACT_NAME, DISPLAY_NAME, EMAIL, LOGIN_PASSWORD, PASSWORD } from './constraints.js';
2
9
  export { hasNewsletterIntent, HONEYPOT_FIELD, isNewsletterIntent, NEWSLETTER_INTENT_PARAM, NEWSLETTER_INTENT_VALUE, withNewsletterIntent } from './transport.js';
package/dist/index.d.ts CHANGED
@@ -1,18 +1,8 @@
1
- export * from './components/account/index.js';
2
- export * from './components/auth/index.js';
3
- export * from './components/contact/index.js';
4
- export * from './components/ui/index.js';
5
- export * from './components/layout/index.js';
6
- export * from './components/weeklies/index.js';
7
- export * from './components/projects/index.js';
8
- export * from './components/team/index.js';
9
- export * from './components/timeline/index.js';
10
- export * from './components/jobs/index.js';
1
+ export * from './primitives.js';
2
+ export * from './chrome.js';
3
+ export * from './content-components.js';
4
+ export * from './community.js';
11
5
  export * from './forms/index.js';
12
6
  export * from './config/index.js';
13
7
  export * from './edit/index.js';
14
- export * from './content/index.js';
15
- export { formatDate, yearOf } from './utils/dates.js';
16
- export { contactCategoryLabel } from './utils/contact.js';
17
- export { MILESTONE_CATEGORY_COLOR, milestoneCategoryLabel } from './utils/milestones.js';
18
8
  export { isPathUnder } from './utils/paths.js';
package/dist/index.js CHANGED
@@ -1,23 +1,15 @@
1
- // Components, by group.
2
- export * from './components/account/index.js';
3
- export * from './components/auth/index.js';
4
- export * from './components/contact/index.js';
5
- export * from './components/ui/index.js';
6
- export * from './components/layout/index.js';
7
- export * from './components/weeklies/index.js';
8
- export * from './components/projects/index.js';
9
- export * from './components/team/index.js';
10
- export * from './components/timeline/index.js';
11
- export * from './components/jobs/index.js';
12
- // The form seam: structural remote-form types, transport constants, field bounds.
1
+ // The whole surface, flat — importable piecemeal through the semantic
2
+ // subpaths: ./primitives, ./forms, ./chrome, ./content, ./community,
3
+ // ./edit, ./config, ./testing.
4
+ export * from './primitives.js';
5
+ export * from './chrome.js';
6
+ export * from './content-components.js';
7
+ export * from './community.js';
8
+ // The form seam: form components, structural remote-form types, transport
9
+ // constants, field bounds.
13
10
  export * from './forms/index.js';
14
11
  // App wiring: configuration context and the edit-mode contract.
15
12
  export * from './config/index.js';
16
13
  export * from './edit/index.js';
17
- // Content shapes and the rich-text mini-format.
18
- export * from './content/index.js';
19
- // Utilities the components share with their host apps.
20
- export { formatDate, yearOf } from './utils/dates.js';
21
- export { contactCategoryLabel } from './utils/contact.js';
22
- export { MILESTONE_CATEGORY_COLOR, milestoneCategoryLabel } from './utils/milestones.js';
14
+ // Path utility shared with host apps.
23
15
  export { isPathUnder } from './utils/paths.js';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Generic UI atoms: buttons, cards, text, media, list controls. Domain-free —
3
+ * everything here renders whatever it is handed.
4
+ */
5
+ export { default as Button } from './components/ui/Button.svelte';
6
+ export { default as CardMedia } from './components/ui/CardMedia.svelte';
7
+ export { default as CardTitle } from './components/ui/CardTitle.svelte';
8
+ export { default as CopyIntro } from './components/ui/CopyIntro.svelte';
9
+ export { default as DateText } from './components/ui/DateText.svelte';
10
+ export { default as DecorShapes } from './components/ui/DecorShapes.svelte';
11
+ export { default as FilterChips } from './components/ui/FilterChips.svelte';
12
+ export { default as GhostButton } from './components/ui/GhostButton.svelte';
13
+ export { default as Link } from './components/ui/Link.svelte';
14
+ export { default as Pagination } from './components/ui/Pagination.svelte';
15
+ export { default as RichText } from './components/ui/RichText.svelte';
16
+ export { default as SearchInput } from './components/ui/SearchInput.svelte';
17
+ export { default as ShareRow } from './components/ui/ShareRow.svelte';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Generic UI atoms: buttons, cards, text, media, list controls. Domain-free —
3
+ * everything here renders whatever it is handed.
4
+ */
5
+ export { default as Button } from './components/ui/Button.svelte';
6
+ export { default as CardMedia } from './components/ui/CardMedia.svelte';
7
+ export { default as CardTitle } from './components/ui/CardTitle.svelte';
8
+ export { default as CopyIntro } from './components/ui/CopyIntro.svelte';
9
+ export { default as DateText } from './components/ui/DateText.svelte';
10
+ export { default as DecorShapes } from './components/ui/DecorShapes.svelte';
11
+ export { default as FilterChips } from './components/ui/FilterChips.svelte';
12
+ export { default as GhostButton } from './components/ui/GhostButton.svelte';
13
+ export { default as Link } from './components/ui/Link.svelte';
14
+ export { default as Pagination } from './components/ui/Pagination.svelte';
15
+ export { default as RichText } from './components/ui/RichText.svelte';
16
+ export { default as SearchInput } from './components/ui/SearchInput.svelte';
17
+ export { default as ShareRow } from './components/ui/ShareRow.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vit-foundation/ui",
3
- "version": "0.2.1",
3
+ "version": "0.5.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",
@@ -37,55 +37,30 @@
37
37
  "svelte": "./dist/index.js",
38
38
  "default": "./dist/index.js"
39
39
  },
40
- "./ui": {
41
- "types": "./dist/components/ui/index.d.ts",
42
- "svelte": "./dist/components/ui/index.js",
43
- "default": "./dist/components/ui/index.js"
40
+ "./primitives": {
41
+ "types": "./dist/primitives.d.ts",
42
+ "svelte": "./dist/primitives.js",
43
+ "default": "./dist/primitives.js"
44
44
  },
45
- "./auth": {
46
- "types": "./dist/components/auth/index.d.ts",
47
- "svelte": "./dist/components/auth/index.js",
48
- "default": "./dist/components/auth/index.js"
49
- },
50
- "./account": {
51
- "types": "./dist/components/account/index.d.ts",
52
- "svelte": "./dist/components/account/index.js",
53
- "default": "./dist/components/account/index.js"
54
- },
55
- "./contact": {
56
- "types": "./dist/components/contact/index.d.ts",
57
- "svelte": "./dist/components/contact/index.js",
58
- "default": "./dist/components/contact/index.js"
59
- },
60
- "./layout": {
61
- "types": "./dist/components/layout/index.d.ts",
62
- "svelte": "./dist/components/layout/index.js",
63
- "default": "./dist/components/layout/index.js"
64
- },
65
- "./weeklies": {
66
- "types": "./dist/components/weeklies/index.d.ts",
67
- "svelte": "./dist/components/weeklies/index.js",
68
- "default": "./dist/components/weeklies/index.js"
69
- },
70
- "./projects": {
71
- "types": "./dist/components/projects/index.d.ts",
72
- "svelte": "./dist/components/projects/index.js",
73
- "default": "./dist/components/projects/index.js"
45
+ "./forms": {
46
+ "types": "./dist/forms/index.d.ts",
47
+ "svelte": "./dist/forms/index.js",
48
+ "default": "./dist/forms/index.js"
74
49
  },
75
- "./team": {
76
- "types": "./dist/components/team/index.d.ts",
77
- "svelte": "./dist/components/team/index.js",
78
- "default": "./dist/components/team/index.js"
50
+ "./chrome": {
51
+ "types": "./dist/chrome.d.ts",
52
+ "svelte": "./dist/chrome.js",
53
+ "default": "./dist/chrome.js"
79
54
  },
80
- "./timeline": {
81
- "types": "./dist/components/timeline/index.d.ts",
82
- "svelte": "./dist/components/timeline/index.js",
83
- "default": "./dist/components/timeline/index.js"
55
+ "./content": {
56
+ "types": "./dist/content-components.d.ts",
57
+ "svelte": "./dist/content-components.js",
58
+ "default": "./dist/content-components.js"
84
59
  },
85
- "./jobs": {
86
- "types": "./dist/components/jobs/index.d.ts",
87
- "svelte": "./dist/components/jobs/index.js",
88
- "default": "./dist/components/jobs/index.js"
60
+ "./community": {
61
+ "types": "./dist/community.d.ts",
62
+ "svelte": "./dist/community.js",
63
+ "default": "./dist/community.js"
89
64
  },
90
65
  "./edit": {
91
66
  "types": "./dist/edit/index.d.ts",
@@ -97,11 +72,6 @@
97
72
  "svelte": "./dist/config/index.js",
98
73
  "default": "./dist/config/index.js"
99
74
  },
100
- "./forms": {
101
- "types": "./dist/forms/index.d.ts",
102
- "svelte": "./dist/forms/index.js",
103
- "default": "./dist/forms/index.js"
104
- },
105
75
  "./testing": {
106
76
  "types": "./dist/testing/remote-form.d.ts",
107
77
  "svelte": "./dist/testing/remote-form.js",