@vit-foundation/ui 0.7.0 → 0.9.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.
@@ -58,6 +58,7 @@
58
58
  <Field
59
59
  id="contact-category"
60
60
  label={msg.contact_categoryLabel()}
61
+ labelEdit={config.messageEdit?.('contact_categoryLabel')}
61
62
  field={f.fields.category}
62
63
  constraint={null}
63
64
  >
@@ -73,6 +74,7 @@
73
74
  <Field
74
75
  id="contact-name"
75
76
  label={msg.contact_nameLabel()}
77
+ labelEdit={config.messageEdit?.('contact_nameLabel')}
76
78
  field={f.fields.name}
77
79
  constraint={CONTACT_NAME}
78
80
  >
@@ -90,6 +92,7 @@
90
92
  <Field
91
93
  id="contact-email"
92
94
  label={msg.contact_emailLabel()}
95
+ labelEdit={config.messageEdit?.('contact_emailLabel')}
93
96
  field={f.fields.email}
94
97
  constraint={EMAIL}
95
98
  >
@@ -107,6 +110,7 @@
107
110
  <Field
108
111
  id="contact-message"
109
112
  label={msg.contact_messageLabel()}
113
+ labelEdit={config.messageEdit?.('contact_messageLabel')}
110
114
  field={f.fields.message}
111
115
  constraint={CONTACT_MESSAGE}
112
116
  >
@@ -10,6 +10,7 @@
10
10
 
11
11
  <script lang="ts">
12
12
  import { getUiConfig } from '../../config/context.js';
13
+ import ActionLabel from '../../edit/ActionLabel.svelte';
13
14
  import type { ProjectCardData } from '../../content/types.js';
14
15
  import { getEditAdapter } from '../../edit/context.js';
15
16
  import Editable from '../../edit/Editable.svelte';
@@ -57,9 +58,16 @@
57
58
  {#snippet children(text, attrs)}<p {...attrs}>{text}</p>{/snippet}
58
59
  </Editable>
59
60
  {#if project.hasStory}
60
- <Link href={`/what-we-do/${project.slug}`} class="more">
61
- {config.messages.common_readMore()}
62
- </Link>
61
+ <ActionLabel
62
+ edit={config.messageEdit?.('common_readMore')}
63
+ value={config.messages.common_readMore()}
64
+ >
65
+ {#snippet control()}
66
+ <Link href={`/what-we-do/${project.slug}`} class="more">
67
+ {config.messages.common_readMore()}
68
+ </Link>
69
+ {/snippet}
70
+ </ActionLabel>
63
71
  {/if}
64
72
  </div>
65
73
  </article>
@@ -10,6 +10,7 @@
10
10
 
11
11
  <script lang="ts">
12
12
  import { getUiConfig } from '../../config/context.js';
13
+ import ActionLabel from '../../edit/ActionLabel.svelte';
13
14
  import type { MilestoneData } from '../../content/types.js';
14
15
  import Editable from '../../edit/Editable.svelte';
15
16
  import { MILESTONE_CATEGORY_COLOR, milestoneCategoryLabel } from '../../utils/milestones.js';
@@ -47,7 +48,12 @@
47
48
 
48
49
  <article style={`--milestone-color: ${color}`}>
49
50
  <span class="dot" aria-hidden="true"></span>
50
- <p class="category">{milestoneCategoryLabel(milestone.category, config.messages)}</p>
51
+ <Editable
52
+ edit={config.messageEdit?.(`category_${milestone.category}`)}
53
+ value={milestoneCategoryLabel(milestone.category, config.messages)}
54
+ >
55
+ {#snippet children(text, attrs)}<p class="category" {...attrs}>{text}</p>{/snippet}
56
+ </Editable>
51
57
  <DateText value={milestone.occurredOn} />
52
58
  <Editable edit={edit?.title} value={milestone.title}>
53
59
  {#snippet children(text, attrs)}<h3 {...attrs}>{text}</h3>{/snippet}
@@ -62,11 +68,20 @@
62
68
  {/if}
63
69
  {#if milestone.linkUrl}
64
70
  <p class="more">
65
- {#if isExternal}
66
- <a href={milestone.linkUrl} rel="external noopener">{config.messages.common_readMore()}</a>
67
- {:else}
68
- <Link href={milestone.linkUrl}>{config.messages.common_readMore()}</Link>
69
- {/if}
71
+ <ActionLabel
72
+ edit={config.messageEdit?.('common_readMore')}
73
+ value={config.messages.common_readMore()}
74
+ >
75
+ {#snippet control()}
76
+ {#if isExternal}
77
+ <a href={milestone.linkUrl} rel="external noopener"
78
+ >{config.messages.common_readMore()}</a
79
+ >
80
+ {:else if milestone.linkUrl}
81
+ <Link href={milestone.linkUrl}>{config.messages.common_readMore()}</Link>
82
+ {/if}
83
+ {/snippet}
84
+ </ActionLabel>
70
85
  </p>
71
86
  {/if}
72
87
  </article>
@@ -1,6 +1,8 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { FieldConstraint } from '../../content/types.js';
4
+ import ActionLabel from '../../edit/ActionLabel.svelte';
5
+ import type { EditDescriptor } from '../../edit/types.js';
4
6
 
5
7
  /** Attributes the control must spread (`{...attrs}`) to stay wired. */
6
8
  interface ControlAttrs {
@@ -46,18 +48,37 @@
46
48
  constraint: FieldConstraint | null;
47
49
  /** Render the label visually hidden (surrounding context already labels it). */
48
50
  hideLabel?: boolean;
51
+ /**
52
+ * Edit descriptor for the LABEL's wording. While editing, only the
53
+ * label swaps for editable text — an editable span is not a <label>,
54
+ * so clicking it no longer focuses the control, and the control itself
55
+ * stays rendered and live. Hidden labels are never offered.
56
+ */
57
+ labelEdit?: EditDescriptor;
49
58
  /** The input/textarea/select, spreading the given attrs. */
50
59
  children: Snippet<[ControlAttrs]>;
51
60
  }
52
61
 
53
- let { id, label, field, constraint, hideLabel = false, children }: Props = $props();
62
+ let {
63
+ id,
64
+ label,
65
+ field,
66
+ constraint,
67
+ hideLabel = false,
68
+ labelEdit = undefined,
69
+ children
70
+ }: Props = $props();
54
71
 
55
72
  const errorMessage = $derived(field.issues()?.[0]?.message);
56
73
  const showError = $derived(errorMessage !== undefined);
57
74
  </script>
58
75
 
59
76
  <div class="field">
60
- <label class:visually-hidden={hideLabel} for={id}>{label}</label>
77
+ <ActionLabel edit={hideLabel ? undefined : labelEdit} value={label}>
78
+ {#snippet control()}
79
+ <label class:visually-hidden={hideLabel} for={id}>{label}</label>
80
+ {/snippet}
81
+ </ActionLabel>
61
82
  {@render children({
62
83
  id,
63
84
  'aria-describedby': showError ? `${id}-error` : undefined,
@@ -1,5 +1,6 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { FieldConstraint } from '../../content/types.js';
3
+ import type { EditDescriptor } from '../../edit/types.js';
3
4
  /** Attributes the control must spread (`{...attrs}`) to stay wired. */
4
5
  interface ControlAttrs {
5
6
  id: string;
@@ -47,6 +48,13 @@ interface Props {
47
48
  constraint: FieldConstraint | null;
48
49
  /** Render the label visually hidden (surrounding context already labels it). */
49
50
  hideLabel?: boolean;
51
+ /**
52
+ * Edit descriptor for the LABEL's wording. While editing, only the
53
+ * label swaps for editable text — an editable span is not a <label>,
54
+ * so clicking it no longer focuses the control, and the control itself
55
+ * stays rendered and live. Hidden labels are never offered.
56
+ */
57
+ labelEdit?: EditDescriptor;
50
58
  /** The input/textarea/select, spreading the given attrs. */
51
59
  children: Snippet<[ControlAttrs]>;
52
60
  }
@@ -1,4 +1,6 @@
1
1
  <script lang="ts">
2
+ import ActionLabel from '../../edit/ActionLabel.svelte';
3
+ import type { EditDescriptor } from '../../edit/types.js';
2
4
  import GhostButton from './GhostButton.svelte';
3
5
 
4
6
  interface Chip {
@@ -13,9 +15,16 @@
13
15
  label: string;
14
16
  /** Called with the toggled value, or null when the active chip is unselected. */
15
17
  onchange: (value: string | null) => void;
18
+ /**
19
+ * Edit descriptors for the chip LABELS — a function because the host
20
+ * owns what a chip's wording is (a theme's localized name, a category
21
+ * message). While editing, a chip with a descriptor renders as
22
+ * editable text instead of a button (see ActionLabel).
23
+ */
24
+ editFor?: (chip: Chip) => EditDescriptor | undefined;
16
25
  }
17
26
 
18
- let { chips, selected, label, onchange }: Props = $props();
27
+ let { chips, selected, label, onchange, editFor = undefined }: Props = $props();
19
28
 
20
29
  function toggle(value: string): void {
21
30
  onchange(selected === value ? null : value);
@@ -24,13 +33,17 @@
24
33
 
25
34
  <div class="chips" role="group" aria-label={label}>
26
35
  {#each chips as chip (chip.value)}
27
- <GhostButton
28
- variant="chip"
29
- aria-pressed={selected === chip.value}
30
- onclick={() => toggle(chip.value)}
31
- >
32
- {chip.label}
33
- </GhostButton>
36
+ <ActionLabel edit={editFor?.(chip)} value={chip.label}>
37
+ {#snippet control()}
38
+ <GhostButton
39
+ variant="chip"
40
+ aria-pressed={selected === chip.value}
41
+ onclick={() => toggle(chip.value)}
42
+ >
43
+ {chip.label}
44
+ </GhostButton>
45
+ {/snippet}
46
+ </ActionLabel>
34
47
  {/each}
35
48
  </div>
36
49
 
@@ -1,3 +1,4 @@
1
+ import type { EditDescriptor } from '../../edit/types.js';
1
2
  interface Chip {
2
3
  value: string;
3
4
  label: string;
@@ -9,6 +10,13 @@ interface Props {
9
10
  label: string;
10
11
  /** Called with the toggled value, or null when the active chip is unselected. */
11
12
  onchange: (value: string | null) => void;
13
+ /**
14
+ * Edit descriptors for the chip LABELS — a function because the host
15
+ * owns what a chip's wording is (a theme's localized name, a category
16
+ * message). While editing, a chip with a descriptor renders as
17
+ * editable text instead of a button (see ActionLabel).
18
+ */
19
+ editFor?: (chip: Chip) => EditDescriptor | undefined;
12
20
  }
13
21
  declare const FilterChips: import("svelte").Component<Props, {}, "">;
14
22
  type FilterChips = ReturnType<typeof FilterChips>;
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { getUiConfig } from '../../config/context.js';
3
3
  import type { SortDirection } from '../../content/types.js';
4
+ import ActionLabel from '../../edit/ActionLabel.svelte';
4
5
 
5
6
  interface Props {
6
7
  value: SortDirection;
@@ -18,13 +19,17 @@
18
19
  }
19
20
  </script>
20
21
 
21
- <label>
22
- <span>{msg.weeklies_sortLabel()}</span>
23
- <select class="control" {value} onchange={handleChange}>
24
- <option value="desc">{msg.weeklies_sortDesc()}</option>
25
- <option value="asc">{msg.weeklies_sortAsc()}</option>
26
- </select>
27
- </label>
22
+ <ActionLabel edit={config.messageEdit?.('weeklies_sortLabel')} value={msg.weeklies_sortLabel()}>
23
+ {#snippet control()}
24
+ <label>
25
+ <span>{msg.weeklies_sortLabel()}</span>
26
+ <select class="control" {value} onchange={handleChange}>
27
+ <option value="desc">{msg.weeklies_sortDesc()}</option>
28
+ <option value="asc">{msg.weeklies_sortAsc()}</option>
29
+ </select>
30
+ </label>
31
+ {/snippet}
32
+ </ActionLabel>
28
33
 
29
34
  <style>
30
35
  label {
@@ -15,7 +15,7 @@ export declare function localize(value: LocalizedText, locale: Locale): string;
15
15
  * The entities whose fields the components can mark editable. Names match
16
16
  * the content tables so an adapter can route a save without a mapping layer.
17
17
  */
18
- export type EditableEntity = 'weeklies' | 'projects' | 'milestones' | 'team_members' | 'job_openings';
18
+ export type EditableEntity = 'weeklies' | 'projects' | 'milestones' | 'team_members' | 'job_openings' | 'themes';
19
19
  /**
20
20
  * The identity of one editable string in the content model: a page copy
21
21
  * block (`page_content` row), one localized field of a domain entity, or one
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vit-foundation/ui",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",