@vit-foundation/ui 0.32.1 → 0.33.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 +1 -1
- package/dist/components/madlib/InlineSelect.svelte +6 -4
- package/dist/components/madlib/Madlib.svelte +2 -2
- package/dist/components/ui/figure/FigureBody.svelte +63 -0
- package/dist/components/ui/figure/FigureBody.svelte.d.ts +29 -0
- package/dist/components/ui/figure/FigureHead.svelte +93 -0
- package/dist/components/ui/figure/FigureHead.svelte.d.ts +27 -0
- package/dist/components/ui/figure/PersonFigure.svelte +253 -0
- package/dist/components/ui/figure/PersonFigure.svelte.d.ts +80 -0
- package/dist/components/ui/figure/paths.d.ts +80 -0
- package/dist/components/ui/figure/paths.js +73 -0
- package/dist/primitives.d.ts +5 -0
- package/dist/primitives.js +6 -0
- package/dist/styles/tokens.css +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ Everything exports flat from the root, and again grouped by role:
|
|
|
27
27
|
|
|
28
28
|
| Entry point | What lives there |
|
|
29
29
|
| --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
30
|
-
| [`/primitives`](./docs/components/primitives.md) | Button, GhostButton, Link, CardMedia, CardTitle, CopyIntro, DateText, DecorShapes, FilterChips, Pagination, RichText, SearchInput, ShareRow
|
|
30
|
+
| [`/primitives`](./docs/components/primitives.md) | Button, GhostButton, Link, CardMedia, CardTitle, CopyIntro, DateText, DecorShapes, FigureBody, FigureHead, FilterChips, Pagination, PersonFigure, RichText, SearchInput, ShareRow |
|
|
31
31
|
| [`/forms`](./docs/forms.md) | Field, SelectField, TextField, FormFeedback, FormErrorFeedback, FormResultSlot, Honeypot, LocaleField, NewsletterIntentField — plus the remote-form seam: structural types, transport constants, field bounds |
|
|
32
32
|
| [`/chrome`](./docs/components/chrome.md) | PageShell, Nav, Footer |
|
|
33
33
|
| [`/content`](./docs/components/content.md) | WeeklieCard, ProjectCard, Timeline, TimelineMilestone, TeamMemberCard, CollaboratorList, JobList, SortSelect, the nine page modules (HomePage … WeeklyPage) — plus the data shapes, the page-copy vocabulary, helpers and the two list rules (createWeeklyList, createUrlFilters) |
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
|
|
146
146
|
.vit-inline-select__trigger {
|
|
147
147
|
display: inline;
|
|
148
|
-
padding: 0
|
|
148
|
+
padding: 0;
|
|
149
149
|
margin: 0;
|
|
150
150
|
border: 0;
|
|
151
151
|
border-bottom: 2px solid currentColor;
|
|
@@ -183,8 +183,10 @@
|
|
|
183
183
|
position: absolute;
|
|
184
184
|
left: 0;
|
|
185
185
|
z-index: var(--vit-madlib-menu-z, 40);
|
|
186
|
-
|
|
187
|
-
|
|
186
|
+
/* Pixel spacing, not em: the menu sits beside sentence text that can be
|
|
187
|
+
24px, and em-scaled rows read as a different, roomier control there. */
|
|
188
|
+
margin: 4px 0 0;
|
|
189
|
+
padding: 4px 0;
|
|
188
190
|
list-style: none;
|
|
189
191
|
width: max-content;
|
|
190
192
|
max-width: 42rem;
|
|
@@ -197,7 +199,7 @@
|
|
|
197
199
|
}
|
|
198
200
|
|
|
199
201
|
.vit-inline-select__option {
|
|
200
|
-
padding:
|
|
202
|
+
padding: 6px 16px;
|
|
201
203
|
font-weight: var(--vit-madlib-control-weight, 700);
|
|
202
204
|
color: var(--vit-madlib-muted-color, color-mix(in srgb, currentColor 35%, transparent));
|
|
203
205
|
cursor: pointer;
|
|
@@ -233,12 +233,12 @@
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
.vit-madlib__separator {
|
|
236
|
-
padding: 0
|
|
236
|
+
padding: 0 6px;
|
|
237
237
|
color: var(--vit-madlib-muted-color, color-mix(in srgb, currentColor 35%, transparent));
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
.vit-madlib__toggle {
|
|
241
|
-
padding: 0 0
|
|
241
|
+
padding: 0 0 2px;
|
|
242
242
|
margin: 0;
|
|
243
243
|
border: 0;
|
|
244
244
|
border-bottom: 2px solid transparent;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component FigureBody
|
|
3
|
+
|
|
4
|
+
The line body: a shoulder line with two hanging arms and a pair of legs in
|
|
5
|
+
a pose, drawn in the composed figure box (`FIGURE_VIEWBOX` in `./paths`).
|
|
6
|
+
No head — a `FigureHead`, a marker or a callout goes in as `children`, which
|
|
7
|
+
render INSIDE the svg and so share its coordinates (neck at `NECK`, shoulder
|
|
8
|
+
line at `SHOULDER`).
|
|
9
|
+
|
|
10
|
+
Decorative: `aria-hidden`, and it never names itself — whatever composes it
|
|
11
|
+
carries the text. Sized by `--vit-figure-width`; the strokes scale with it
|
|
12
|
+
because `--vit-figure-stroke` is in viewBox units.
|
|
13
|
+
|
|
14
|
+
@property arms - Arm pose
|
|
15
|
+
@property legs - Leg pose
|
|
16
|
+
@property class - Extra classes on the svg
|
|
17
|
+
@property children - Drawn inside the svg, after the body
|
|
18
|
+
-->
|
|
19
|
+
<script lang="ts">
|
|
20
|
+
import type { Snippet } from 'svelte';
|
|
21
|
+
import { ARMS, ARMS_AT, FIGURE_VIEWBOX, LEGS, LEGS_AT } from './paths.js';
|
|
22
|
+
import type { ArmsPose, LegsPose } from './paths.js';
|
|
23
|
+
|
|
24
|
+
interface Props {
|
|
25
|
+
arms?: ArmsPose;
|
|
26
|
+
legs?: LegsPose;
|
|
27
|
+
class?: string;
|
|
28
|
+
children?: Snippet;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let { arms = 'down', legs = 'standing', class: className = '', children }: Props = $props();
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<svg
|
|
35
|
+
class="vit-figure-body {className}"
|
|
36
|
+
viewBox={FIGURE_VIEWBOX}
|
|
37
|
+
aria-hidden="true"
|
|
38
|
+
focusable="false"
|
|
39
|
+
>
|
|
40
|
+
<g class="vit-figure-body__strokes">
|
|
41
|
+
<path d={ARMS[arms]} transform={ARMS_AT} />
|
|
42
|
+
<path d={LEGS[legs]} transform={LEGS_AT} />
|
|
43
|
+
</g>
|
|
44
|
+
{@render children?.()}
|
|
45
|
+
</svg>
|
|
46
|
+
|
|
47
|
+
<style>
|
|
48
|
+
.vit-figure-body {
|
|
49
|
+
display: block;
|
|
50
|
+
width: var(--vit-figure-width, 10rem);
|
|
51
|
+
height: auto;
|
|
52
|
+
/* A callout, or a raised arm at a small width, may leave the box. */
|
|
53
|
+
overflow: visible;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.vit-figure-body__strokes {
|
|
57
|
+
fill: none;
|
|
58
|
+
stroke: var(--vit-figure-ink, var(--color-ink));
|
|
59
|
+
stroke-width: var(--vit-figure-stroke, 4);
|
|
60
|
+
stroke-linecap: round;
|
|
61
|
+
stroke-linejoin: round;
|
|
62
|
+
}
|
|
63
|
+
</style>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { ArmsPose, LegsPose } from './paths.js';
|
|
3
|
+
interface Props {
|
|
4
|
+
arms?: ArmsPose;
|
|
5
|
+
legs?: LegsPose;
|
|
6
|
+
class?: string;
|
|
7
|
+
children?: Snippet;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* FigureBody
|
|
11
|
+
*
|
|
12
|
+
* The line body: a shoulder line with two hanging arms and a pair of legs in
|
|
13
|
+
* a pose, drawn in the composed figure box (`FIGURE_VIEWBOX` in `./paths`).
|
|
14
|
+
* No head — a `FigureHead`, a marker or a callout goes in as `children`, which
|
|
15
|
+
* render INSIDE the svg and so share its coordinates (neck at `NECK`, shoulder
|
|
16
|
+
* line at `SHOULDER`).
|
|
17
|
+
*
|
|
18
|
+
* Decorative: `aria-hidden`, and it never names itself — whatever composes it
|
|
19
|
+
* carries the text. Sized by `--vit-figure-width`; the strokes scale with it
|
|
20
|
+
* because `--vit-figure-stroke` is in viewBox units.
|
|
21
|
+
*
|
|
22
|
+
* @property arms - Arm pose
|
|
23
|
+
* @property legs - Leg pose
|
|
24
|
+
* @property class - Extra classes on the svg
|
|
25
|
+
* @property children - Drawn inside the svg, after the body
|
|
26
|
+
*/
|
|
27
|
+
declare const FigureBody: import("svelte").Component<Props, {}, "">;
|
|
28
|
+
type FigureBody = ReturnType<typeof FigureBody>;
|
|
29
|
+
export default FigureBody;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component FigureHead
|
|
3
|
+
|
|
4
|
+
The head, three ways: a cut-out photo with a transparent background sat on
|
|
5
|
+
the neck, a portrait masked into a circle, or — with no photo, or one that
|
|
6
|
+
fails to load — the survey's drawn outline. An svg FRAGMENT: it renders
|
|
7
|
+
inside a `FigureBody` (or any svg in the figure box) and places itself on
|
|
8
|
+
`NECK`.
|
|
9
|
+
|
|
10
|
+
The photo carries no alt and the outline no title: the head is part of a
|
|
11
|
+
decorative drawing, and the name belongs to whatever composes it.
|
|
12
|
+
|
|
13
|
+
@property photo - The photo; absent or failing to load, the head is drawn
|
|
14
|
+
@property photoShape - `cutout` sits a transparent cut-out on the neck; `circle` masks a portrait
|
|
15
|
+
@property head - The drawn outline, used when there is no photo
|
|
16
|
+
@property headScale - Scales the photo about the neck point, so the chin stays put
|
|
17
|
+
-->
|
|
18
|
+
<svelte:options namespace="svg" />
|
|
19
|
+
|
|
20
|
+
<script lang="ts">
|
|
21
|
+
import { HEAD_AT, HEADS, NECK } from './paths.js';
|
|
22
|
+
import type { HeadShape } from './paths.js';
|
|
23
|
+
|
|
24
|
+
interface Props {
|
|
25
|
+
photo?: string | null;
|
|
26
|
+
photoShape?: 'cutout' | 'circle';
|
|
27
|
+
head?: HeadShape;
|
|
28
|
+
headScale?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let { photo, photoShape = 'cutout', head = 'round', headScale = 1 }: Props = $props();
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The photo that failed to load, so a broken URL degrades to the drawn head
|
|
35
|
+
* rather than an empty neck. Keyed by value, not a boolean (the CardMedia
|
|
36
|
+
* rule): a new URL gets a fresh attempt.
|
|
37
|
+
*/
|
|
38
|
+
let failedSrc = $state<string | null>(null);
|
|
39
|
+
const showPhoto = $derived(Boolean(photo) && photo !== failedSrc);
|
|
40
|
+
|
|
41
|
+
// A clipPath is referenced by id, and a team page renders many heads.
|
|
42
|
+
const uid = $props.id();
|
|
43
|
+
const clipId = `${uid}-head`;
|
|
44
|
+
const transform = $derived(
|
|
45
|
+
headScale === 1
|
|
46
|
+
? undefined
|
|
47
|
+
: `translate(${NECK.x} ${NECK.y}) scale(${headScale}) translate(${-NECK.x} ${-NECK.y})`
|
|
48
|
+
);
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
{#if !showPhoto}
|
|
52
|
+
<path class="vit-figure-head" d={HEADS[head]} transform={HEAD_AT} />
|
|
53
|
+
{:else if photoShape === 'circle'}
|
|
54
|
+
<clipPath id={clipId}>
|
|
55
|
+
<circle cx={NECK.x} cy="48" r="34" />
|
|
56
|
+
</clipPath>
|
|
57
|
+
<image
|
|
58
|
+
class="vit-figure-head__photo"
|
|
59
|
+
href={photo}
|
|
60
|
+
x={NECK.x - 34}
|
|
61
|
+
y="14"
|
|
62
|
+
width="68"
|
|
63
|
+
height="68"
|
|
64
|
+
preserveAspectRatio="xMidYMid slice"
|
|
65
|
+
clip-path="url(#{clipId})"
|
|
66
|
+
{transform}
|
|
67
|
+
onerror={() => (failedSrc = photo ?? null)}
|
|
68
|
+
/>
|
|
69
|
+
{:else}
|
|
70
|
+
<!-- Bottom-anchored (`xMidYMax`): whatever the cut-out's proportions, its
|
|
71
|
+
chin lands on the neck, 8 over the shoulder line. The box is the
|
|
72
|
+
shoulders' width — a head that wide is the mockup's proportion. -->
|
|
73
|
+
<image
|
|
74
|
+
class="vit-figure-head__photo"
|
|
75
|
+
href={photo}
|
|
76
|
+
x={NECK.x - 38}
|
|
77
|
+
y="0"
|
|
78
|
+
width="76"
|
|
79
|
+
height="84"
|
|
80
|
+
preserveAspectRatio="xMidYMax meet"
|
|
81
|
+
{transform}
|
|
82
|
+
onerror={() => (failedSrc = photo ?? null)}
|
|
83
|
+
/>
|
|
84
|
+
{/if}
|
|
85
|
+
|
|
86
|
+
<style>
|
|
87
|
+
.vit-figure-head {
|
|
88
|
+
fill: var(--color-surface);
|
|
89
|
+
stroke: var(--vit-figure-ink, var(--color-ink));
|
|
90
|
+
stroke-width: var(--vit-figure-stroke, 4);
|
|
91
|
+
stroke-linejoin: round;
|
|
92
|
+
}
|
|
93
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { HeadShape } from './paths.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
photo?: string | null;
|
|
4
|
+
photoShape?: 'cutout' | 'circle';
|
|
5
|
+
head?: HeadShape;
|
|
6
|
+
headScale?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* FigureHead
|
|
10
|
+
*
|
|
11
|
+
* The head, three ways: a cut-out photo with a transparent background sat on
|
|
12
|
+
* the neck, a portrait masked into a circle, or — with no photo, or one that
|
|
13
|
+
* fails to load — the survey's drawn outline. An svg FRAGMENT: it renders
|
|
14
|
+
* inside a `FigureBody` (or any svg in the figure box) and places itself on
|
|
15
|
+
* `NECK`.
|
|
16
|
+
*
|
|
17
|
+
* The photo carries no alt and the outline no title: the head is part of a
|
|
18
|
+
* decorative drawing, and the name belongs to whatever composes it.
|
|
19
|
+
*
|
|
20
|
+
* @property photo - The photo; absent or failing to load, the head is drawn
|
|
21
|
+
* @property photoShape - `cutout` sits a transparent cut-out on the neck; `circle` masks a portrait
|
|
22
|
+
* @property head - The drawn outline, used when there is no photo
|
|
23
|
+
* @property headScale - Scales the photo about the neck point, so the chin stays put
|
|
24
|
+
*/
|
|
25
|
+
declare const FigureHead: import("svelte").Component<Props, {}, "">;
|
|
26
|
+
type FigureHead = ReturnType<typeof FigureHead>;
|
|
27
|
+
export default FigureHead;
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component PersonFigure
|
|
3
|
+
|
|
4
|
+
A person: a `FigureBody` in a pose, a `FigureHead` on its neck, and beside
|
|
5
|
+
them the name and role over a short rule, joined to the shoulder by a
|
|
6
|
+
diagonal callout line. A bio, when there is one, reveals under the rule on
|
|
7
|
+
hover, keyboard focus or tap.
|
|
8
|
+
|
|
9
|
+
This is the composition; the parts stand alone. `./paths` holds the drawing
|
|
10
|
+
and the callout arithmetic, `FigureBody` the strokes, `FigureHead` the head's
|
|
11
|
+
three modes. What is here is what only the whole needs: the grid that pins
|
|
12
|
+
the caption's baseline to the rule, and the reveal.
|
|
13
|
+
|
|
14
|
+
## Accessibility
|
|
15
|
+
The drawing is decorative — the svg is `aria-hidden`, the photo has no alt —
|
|
16
|
+
and the name in the caption is the only text: the `Logo` rule, a mark never
|
|
17
|
+
names itself. The bio is hidden by opacity, not removed, so a screen reader
|
|
18
|
+
reads it without hovering; the figure takes `tabindex="0"` only when there
|
|
19
|
+
is a bio to reveal.
|
|
20
|
+
|
|
21
|
+
## Sizing
|
|
22
|
+
Set `--vit-figure-width` on the figure or an ancestor. The art, the callout
|
|
23
|
+
run and the height of the rule above the baseline all derive from it; the
|
|
24
|
+
text is in rem. Below about 8rem the two label lines outgrow the room above
|
|
25
|
+
a top-aligned rule — lower `--vit-figure-name-size`/`--vit-figure-role-size`
|
|
26
|
+
or align the label `bottom`. The callout leaves the art's box on purpose
|
|
27
|
+
(`overflow: visible`), so an ancestor clipping its overflow hides it.
|
|
28
|
+
|
|
29
|
+
| property | default |
|
|
30
|
+
|-----------------------------|---------------------|
|
|
31
|
+
| `--vit-figure-width` | `10rem` |
|
|
32
|
+
| `--vit-figure-ink` | `var(--color-ink)` |
|
|
33
|
+
| `--vit-figure-stroke` | `4` (viewBox units) |
|
|
34
|
+
| `--vit-figure-callout-ink` | `var(--color-ink)` |
|
|
35
|
+
| `--vit-figure-name-size` | `var(--text-base)` |
|
|
36
|
+
| `--vit-figure-role-size` | `var(--text-sm)` |
|
|
37
|
+
| `--vit-figure-bio-width` | `14rem` |
|
|
38
|
+
|
|
39
|
+
@property name - The person's name; the figure's accessible text
|
|
40
|
+
@property role - The line under the name
|
|
41
|
+
@property bio - Revealed under the rule on hover, focus or tap
|
|
42
|
+
@property photo - The head photo; absent or failing to load, the head is drawn
|
|
43
|
+
@property photoShape - `cutout` sits a transparent cut-out on the neck; `circle` masks a portrait
|
|
44
|
+
@property head - Drawn head outline used when there is no photo
|
|
45
|
+
@property arms - Arm pose
|
|
46
|
+
@property legs - Leg pose
|
|
47
|
+
@property headScale - Scales the photo head about the neck point
|
|
48
|
+
@property labelSide - Which side of the figure the label sits on
|
|
49
|
+
@property labelAlign - `top` puts the rule beside the head, `bottom` beside the legs
|
|
50
|
+
@property class - Extra classes on the figure
|
|
51
|
+
@property children - An extra marker laid over the body (position it absolutely)
|
|
52
|
+
-->
|
|
53
|
+
<script lang="ts">
|
|
54
|
+
import type { Snippet } from 'svelte';
|
|
55
|
+
import FigureBody from './FigureBody.svelte';
|
|
56
|
+
import FigureHead from './FigureHead.svelte';
|
|
57
|
+
import { calloutPath } from './paths.js';
|
|
58
|
+
import type { ArmsPose, HeadShape, LabelAlign, LabelSide, LegsPose } from './paths.js';
|
|
59
|
+
|
|
60
|
+
interface Props {
|
|
61
|
+
/** The figure's only accessible text — the drawing never names itself. */
|
|
62
|
+
name: string;
|
|
63
|
+
role?: string;
|
|
64
|
+
/** Revealed under the rule on hover, focus or tap. Absent: nothing to reveal, no tabindex. */
|
|
65
|
+
bio?: string | null;
|
|
66
|
+
/** Absent, or failing to load, the head is drawn instead. */
|
|
67
|
+
photo?: string | null;
|
|
68
|
+
/** `cutout` = a transparent-background cut-out sat on the neck; `circle` = a portrait masked into a circle. */
|
|
69
|
+
photoShape?: 'cutout' | 'circle';
|
|
70
|
+
/** The drawn head, used when there is no photo. */
|
|
71
|
+
head?: HeadShape;
|
|
72
|
+
arms?: ArmsPose;
|
|
73
|
+
legs?: LegsPose;
|
|
74
|
+
/** Scales the photo head about the neck point, so the chin stays put. */
|
|
75
|
+
headScale?: number;
|
|
76
|
+
labelSide?: LabelSide;
|
|
77
|
+
/** `top` puts the rule beside the head, `bottom` beside the legs. */
|
|
78
|
+
labelAlign?: LabelAlign;
|
|
79
|
+
class?: string;
|
|
80
|
+
/** An extra marker over the body; position it absolutely inside the art's box. */
|
|
81
|
+
children?: Snippet;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let {
|
|
85
|
+
name,
|
|
86
|
+
role,
|
|
87
|
+
bio,
|
|
88
|
+
photo,
|
|
89
|
+
photoShape = 'cutout',
|
|
90
|
+
head = 'round',
|
|
91
|
+
arms = 'down',
|
|
92
|
+
legs = 'standing',
|
|
93
|
+
headScale = 1,
|
|
94
|
+
labelSide = 'right',
|
|
95
|
+
labelAlign = 'top',
|
|
96
|
+
class: className = '',
|
|
97
|
+
children
|
|
98
|
+
}: Props = $props();
|
|
99
|
+
|
|
100
|
+
const callout = $derived(calloutPath(labelSide, labelAlign));
|
|
101
|
+
</script>
|
|
102
|
+
|
|
103
|
+
<!-- Focusable only when there is a bio to reveal; `figure` is not interactive
|
|
104
|
+
otherwise, and a focus stop with nothing behind it is noise. -->
|
|
105
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
106
|
+
<figure
|
|
107
|
+
class="vit-figure {className}"
|
|
108
|
+
data-side={labelSide}
|
|
109
|
+
data-align={labelAlign}
|
|
110
|
+
tabindex={bio ? 0 : undefined}
|
|
111
|
+
>
|
|
112
|
+
<FigureBody {arms} {legs} class="vit-figure__art">
|
|
113
|
+
<!-- After the body, so the head covers the middle of the shoulder line. -->
|
|
114
|
+
<FigureHead {photo} {photoShape} {head} {headScale} />
|
|
115
|
+
<path class="vit-figure__callout" d={callout} vector-effect="non-scaling-stroke" />
|
|
116
|
+
</FigureBody>
|
|
117
|
+
{#if children}
|
|
118
|
+
<div class="vit-figure__extra">{@render children()}</div>
|
|
119
|
+
{/if}
|
|
120
|
+
<figcaption class="vit-figure__label">
|
|
121
|
+
<strong class="vit-figure__name">{name}</strong>
|
|
122
|
+
{#if role}<span class="vit-figure__role">{role}</span>{/if}
|
|
123
|
+
{#if bio}<p class="vit-figure__bio">{bio}</p>{/if}
|
|
124
|
+
</figcaption>
|
|
125
|
+
</figure>
|
|
126
|
+
|
|
127
|
+
<style>
|
|
128
|
+
.vit-figure {
|
|
129
|
+
/* One viewBox unit, so the run and the rule's row follow the width.
|
|
130
|
+
The 24 and 90 are `CALLOUT_RUN` and `CALLOUT_RULE` in paths.ts. */
|
|
131
|
+
--_w: var(--vit-figure-width, 10rem);
|
|
132
|
+
--_u: calc(var(--_w) / 153);
|
|
133
|
+
--_run: calc(var(--_u) * 24);
|
|
134
|
+
--_rule-y: 50;
|
|
135
|
+
position: relative;
|
|
136
|
+
display: inline-grid;
|
|
137
|
+
align-items: start;
|
|
138
|
+
/* The first row ends exactly where the callout's rule is drawn, and the
|
|
139
|
+
caption sits on that edge — so the rule underlines the role whatever
|
|
140
|
+
the font size, and the text grows upward from it. */
|
|
141
|
+
grid-template-rows: calc(var(--_u) * var(--_rule-y)) auto;
|
|
142
|
+
margin: 0;
|
|
143
|
+
color: var(--color-ink);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.vit-figure[data-align='bottom'] {
|
|
147
|
+
--_rule-y: 200;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.vit-figure[data-side='right'] {
|
|
151
|
+
grid-template-columns: var(--_w) var(--_run) auto;
|
|
152
|
+
grid-template-areas:
|
|
153
|
+
'art run label'
|
|
154
|
+
'art run .';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.vit-figure[data-side='left'] {
|
|
158
|
+
grid-template-columns: auto var(--_run) var(--_w);
|
|
159
|
+
grid-template-areas:
|
|
160
|
+
'label run art'
|
|
161
|
+
'. run art';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* The body's svg is another component's element, hence the :global. */
|
|
165
|
+
.vit-figure > :global(.vit-figure__art) {
|
|
166
|
+
grid-area: art;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.vit-figure__callout {
|
|
170
|
+
fill: none;
|
|
171
|
+
stroke: var(--vit-figure-callout-ink, var(--color-ink));
|
|
172
|
+
stroke-width: 1;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.vit-figure__label {
|
|
176
|
+
grid-area: label;
|
|
177
|
+
position: relative;
|
|
178
|
+
display: flex;
|
|
179
|
+
flex-direction: column;
|
|
180
|
+
align-self: end;
|
|
181
|
+
min-width: calc(var(--_u) * 90);
|
|
182
|
+
padding-bottom: var(--space-1);
|
|
183
|
+
line-height: var(--leading-tight);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.vit-figure[data-side='left'] .vit-figure__label {
|
|
187
|
+
align-items: flex-end;
|
|
188
|
+
text-align: right;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.vit-figure__name {
|
|
192
|
+
font-size: var(--vit-figure-name-size, var(--text-base));
|
|
193
|
+
font-weight: 700;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.vit-figure__role {
|
|
197
|
+
font-size: var(--vit-figure-role-size, var(--text-sm));
|
|
198
|
+
color: var(--color-ink-secondary);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.vit-figure__bio {
|
|
202
|
+
position: absolute;
|
|
203
|
+
top: 100%;
|
|
204
|
+
inset-inline-start: 0;
|
|
205
|
+
width: var(--vit-figure-bio-width, 14rem);
|
|
206
|
+
margin: var(--space-1) 0 0;
|
|
207
|
+
font-size: var(--text-sm);
|
|
208
|
+
line-height: var(--leading-body);
|
|
209
|
+
color: var(--color-ink-secondary);
|
|
210
|
+
opacity: 0;
|
|
211
|
+
transform: translateY(-0.25rem);
|
|
212
|
+
pointer-events: none;
|
|
213
|
+
transition:
|
|
214
|
+
opacity var(--transition-fast),
|
|
215
|
+
transform var(--transition-fast);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.vit-figure[data-side='left'] .vit-figure__bio {
|
|
219
|
+
inset-inline-start: auto;
|
|
220
|
+
inset-inline-end: 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.vit-figure:hover .vit-figure__bio,
|
|
224
|
+
.vit-figure:focus-within .vit-figure__bio {
|
|
225
|
+
opacity: 1;
|
|
226
|
+
transform: none;
|
|
227
|
+
pointer-events: auto;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.vit-figure:focus-visible {
|
|
231
|
+
outline: 2px solid var(--color-brand);
|
|
232
|
+
outline-offset: 4px;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.vit-figure__extra {
|
|
236
|
+
grid-area: art;
|
|
237
|
+
position: relative;
|
|
238
|
+
width: var(--_w);
|
|
239
|
+
height: 100%;
|
|
240
|
+
pointer-events: none;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.vit-figure__extra > :global(*) {
|
|
244
|
+
pointer-events: auto;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
@media (prefers-reduced-motion: reduce) {
|
|
248
|
+
.vit-figure__bio {
|
|
249
|
+
transition: none;
|
|
250
|
+
transform: none;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
</style>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { ArmsPose, HeadShape, LabelAlign, LabelSide, LegsPose } from './paths.js';
|
|
3
|
+
interface Props {
|
|
4
|
+
/** The figure's only accessible text — the drawing never names itself. */
|
|
5
|
+
name: string;
|
|
6
|
+
role?: string;
|
|
7
|
+
/** Revealed under the rule on hover, focus or tap. Absent: nothing to reveal, no tabindex. */
|
|
8
|
+
bio?: string | null;
|
|
9
|
+
/** Absent, or failing to load, the head is drawn instead. */
|
|
10
|
+
photo?: string | null;
|
|
11
|
+
/** `cutout` = a transparent-background cut-out sat on the neck; `circle` = a portrait masked into a circle. */
|
|
12
|
+
photoShape?: 'cutout' | 'circle';
|
|
13
|
+
/** The drawn head, used when there is no photo. */
|
|
14
|
+
head?: HeadShape;
|
|
15
|
+
arms?: ArmsPose;
|
|
16
|
+
legs?: LegsPose;
|
|
17
|
+
/** Scales the photo head about the neck point, so the chin stays put. */
|
|
18
|
+
headScale?: number;
|
|
19
|
+
labelSide?: LabelSide;
|
|
20
|
+
/** `top` puts the rule beside the head, `bottom` beside the legs. */
|
|
21
|
+
labelAlign?: LabelAlign;
|
|
22
|
+
class?: string;
|
|
23
|
+
/** An extra marker over the body; position it absolutely inside the art's box. */
|
|
24
|
+
children?: Snippet;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* PersonFigure
|
|
28
|
+
*
|
|
29
|
+
* A person: a `FigureBody` in a pose, a `FigureHead` on its neck, and beside
|
|
30
|
+
* them the name and role over a short rule, joined to the shoulder by a
|
|
31
|
+
* diagonal callout line. A bio, when there is one, reveals under the rule on
|
|
32
|
+
* hover, keyboard focus or tap.
|
|
33
|
+
*
|
|
34
|
+
* This is the composition; the parts stand alone. `./paths` holds the drawing
|
|
35
|
+
* and the callout arithmetic, `FigureBody` the strokes, `FigureHead` the head's
|
|
36
|
+
* three modes. What is here is what only the whole needs: the grid that pins
|
|
37
|
+
* the caption's baseline to the rule, and the reveal.
|
|
38
|
+
*
|
|
39
|
+
* ## Accessibility
|
|
40
|
+
* The drawing is decorative — the svg is `aria-hidden`, the photo has no alt —
|
|
41
|
+
* and the name in the caption is the only text: the `Logo` rule, a mark never
|
|
42
|
+
* names itself. The bio is hidden by opacity, not removed, so a screen reader
|
|
43
|
+
* reads it without hovering; the figure takes `tabindex="0"` only when there
|
|
44
|
+
* is a bio to reveal.
|
|
45
|
+
*
|
|
46
|
+
* ## Sizing
|
|
47
|
+
* Set `--vit-figure-width` on the figure or an ancestor. The art, the callout
|
|
48
|
+
* run and the height of the rule above the baseline all derive from it; the
|
|
49
|
+
* text is in rem. Below about 8rem the two label lines outgrow the room above
|
|
50
|
+
* a top-aligned rule — lower `--vit-figure-name-size`/`--vit-figure-role-size`
|
|
51
|
+
* or align the label `bottom`. The callout leaves the art's box on purpose
|
|
52
|
+
* (`overflow: visible`), so an ancestor clipping its overflow hides it.
|
|
53
|
+
*
|
|
54
|
+
* | property | default |
|
|
55
|
+
* |-----------------------------|---------------------|
|
|
56
|
+
* | `--vit-figure-width` | `10rem` |
|
|
57
|
+
* | `--vit-figure-ink` | `var(--color-ink)` |
|
|
58
|
+
* | `--vit-figure-stroke` | `4` (viewBox units) |
|
|
59
|
+
* | `--vit-figure-callout-ink` | `var(--color-ink)` |
|
|
60
|
+
* | `--vit-figure-name-size` | `var(--text-base)` |
|
|
61
|
+
* | `--vit-figure-role-size` | `var(--text-sm)` |
|
|
62
|
+
* | `--vit-figure-bio-width` | `14rem` |
|
|
63
|
+
*
|
|
64
|
+
* @property name - The person's name; the figure's accessible text
|
|
65
|
+
* @property role - The line under the name
|
|
66
|
+
* @property bio - Revealed under the rule on hover, focus or tap
|
|
67
|
+
* @property photo - The head photo; absent or failing to load, the head is drawn
|
|
68
|
+
* @property photoShape - `cutout` sits a transparent cut-out on the neck; `circle` masks a portrait
|
|
69
|
+
* @property head - Drawn head outline used when there is no photo
|
|
70
|
+
* @property arms - Arm pose
|
|
71
|
+
* @property legs - Leg pose
|
|
72
|
+
* @property headScale - Scales the photo head about the neck point
|
|
73
|
+
* @property labelSide - Which side of the figure the label sits on
|
|
74
|
+
* @property labelAlign - `top` puts the rule beside the head, `bottom` beside the legs
|
|
75
|
+
* @property class - Extra classes on the figure
|
|
76
|
+
* @property children - An extra marker laid over the body (position it absolutely)
|
|
77
|
+
*/
|
|
78
|
+
declare const PersonFigure: import("svelte").Component<Props, {}, "">;
|
|
79
|
+
type PersonFigure = ReturnType<typeof PersonFigure>;
|
|
80
|
+
export default PersonFigure;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The drawing behind `FigureBody`, `FigureHead` and `PersonFigure`: the
|
|
3
|
+
* survey's stroke paths, where each part sits in the composed box, and the
|
|
4
|
+
* arithmetic of the label's callout. Data and arithmetic only, so it tests in
|
|
5
|
+
* node and a host that wants line people without heads or labels — a chart, a
|
|
6
|
+
* decorative band — can draw from the same set.
|
|
7
|
+
*
|
|
8
|
+
* The paths are the ones `mwc-enquesta-bretxa-digital` built its survey
|
|
9
|
+
* avatars from (`paths.json`). They are closed sets: extending one is a
|
|
10
|
+
* package change, so every consumer's `ArmsPose` stays honest (the `Icon`
|
|
11
|
+
* rule).
|
|
12
|
+
*/
|
|
13
|
+
/** Shoulder line and two hanging arms, in the survey's 153×111 torso box. */
|
|
14
|
+
export declare const ARMS: {
|
|
15
|
+
readonly down: "M27 107L39.375 8H113.625L126 107";
|
|
16
|
+
readonly raised: "M7 25L32.9376 57.5L39.125 8H113.375L119.563 57.5L145.5 25";
|
|
17
|
+
readonly 'one-bent': "M51.899 91.3976L19 53.7671L38.9562 8H113.565L126 107";
|
|
18
|
+
};
|
|
19
|
+
/** Two legs in the survey's 91×187 box (its adult set). */
|
|
20
|
+
export declare const LEGS: {
|
|
21
|
+
readonly standing: "M21 7V179H8M70 7V179H82";
|
|
22
|
+
readonly walking: "M69 7L85 129.857L69 179H81.4218M21 7L36 129.857L21 179H32.6453";
|
|
23
|
+
readonly stride: "M21 7V80.7143L38 179H25.0024M71 7V80.7143L54 179H65.3333";
|
|
24
|
+
readonly step: "M21 7V179H8M70 7L85 129.857L70 179H81.6545";
|
|
25
|
+
readonly kneel: "M21 7V179H9M71 7V130.087L46 168.589V182";
|
|
26
|
+
readonly sit: "M21 7V130.087L45 168.589V182M69 7L54 129.857L69 179H57.3546";
|
|
27
|
+
};
|
|
28
|
+
/** Drawn head outlines in the survey's 49×48 box, for a figure with no photo. */
|
|
29
|
+
export declare const HEADS: {
|
|
30
|
+
readonly round: "M25 41C33.8366 41 41 33.6127 41 24.5C41 15.3873 33.8366 8 25 8C16.1634 8 9 15.3873 9 24.5C9 33.6127 16.1634 41 25 41Z";
|
|
31
|
+
readonly cup: "M8 8V24.4952C7.99874 26.6662 8.4255 28.8161 9.25592 30.8216C10.0863 32.8271 11.3042 34.6488 12.8393 36.1822C14.3745 37.7155 16.1968 38.9304 18.2019 39.7571C20.207 40.5839 22.3554 41.0062 24.5239 40.9999C28.8936 40.9999 33.0843 39.262 36.1742 36.1686C39.2641 33.0751 41 28.8795 41 24.5047V8.00955L8 8Z";
|
|
32
|
+
readonly d: "M27.571 8H38V41H27.571C23.1726 40.9949 18.9563 39.2535 15.8489 36.1585C12.7415 33.0636 10.9975 28.8684 11 24.4952C11 20.1237 12.7453 15.931 15.8524 12.8381C18.9595 9.74519 23.1743 8.00507 27.571 8V8Z";
|
|
33
|
+
};
|
|
34
|
+
export type ArmsPose = keyof typeof ARMS;
|
|
35
|
+
export type LegsPose = keyof typeof LEGS;
|
|
36
|
+
export type HeadShape = keyof typeof HEADS;
|
|
37
|
+
/**
|
|
38
|
+
* The composed box. The survey stacked three svgs on a grid (rows 48 / 111 /
|
|
39
|
+
* 100, the legs' 187 hanging out of the last row); this is that stack
|
|
40
|
+
* flattened into one viewBox, plus 20 of headroom so a photo can rise above
|
|
41
|
+
* where the drawn head would be. Landmarks: shoulder line y 76 from x 39 to
|
|
42
|
+
* 114, legs from y 99, feet at y 271.
|
|
43
|
+
*/
|
|
44
|
+
export declare const FIGURE_WIDTH = 153;
|
|
45
|
+
export declare const FIGURE_HEIGHT = 280;
|
|
46
|
+
export declare const FIGURE_VIEWBOX = "0 0 153 280";
|
|
47
|
+
export declare const HEAD_AT = "translate(52 20)";
|
|
48
|
+
export declare const ARMS_AT = "translate(0 68)";
|
|
49
|
+
export declare const LEGS_AT = "translate(31 92)";
|
|
50
|
+
/** The neck: a photo's chin lands here, and `headScale` scales about it. */
|
|
51
|
+
export declare const NECK: {
|
|
52
|
+
readonly x: 76.5;
|
|
53
|
+
readonly y: 84;
|
|
54
|
+
};
|
|
55
|
+
/** Where the callout leaves the body — the ends of the shoulder line. */
|
|
56
|
+
export declare const SHOULDER: {
|
|
57
|
+
readonly y: 76;
|
|
58
|
+
readonly left: 39.5;
|
|
59
|
+
readonly right: 113.5;
|
|
60
|
+
};
|
|
61
|
+
export type LabelSide = 'left' | 'right';
|
|
62
|
+
export type LabelAlign = 'top' | 'bottom';
|
|
63
|
+
/**
|
|
64
|
+
* The height of the label's rule: beside the head, or beside the legs.
|
|
65
|
+
* `PersonFigure` sizes the caption's grid row to it, so the caption's
|
|
66
|
+
* baseline meets the rule whatever the font size.
|
|
67
|
+
*/
|
|
68
|
+
export declare const RULE_Y: {
|
|
69
|
+
readonly top: 50;
|
|
70
|
+
readonly bottom: 200;
|
|
71
|
+
};
|
|
72
|
+
/** How far the callout runs out of the art before the rule, and the rule's length. */
|
|
73
|
+
export declare const CALLOUT_RUN = 24;
|
|
74
|
+
export declare const CALLOUT_RULE = 90;
|
|
75
|
+
/**
|
|
76
|
+
* Shoulder → the rule's inner end → along the rule. Mirrored for a label on
|
|
77
|
+
* the left, where it runs into negative x — the art's svg draws with
|
|
78
|
+
* `overflow: visible` for exactly this.
|
|
79
|
+
*/
|
|
80
|
+
export declare function calloutPath(side: LabelSide, align: LabelAlign): string;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The drawing behind `FigureBody`, `FigureHead` and `PersonFigure`: the
|
|
3
|
+
* survey's stroke paths, where each part sits in the composed box, and the
|
|
4
|
+
* arithmetic of the label's callout. Data and arithmetic only, so it tests in
|
|
5
|
+
* node and a host that wants line people without heads or labels — a chart, a
|
|
6
|
+
* decorative band — can draw from the same set.
|
|
7
|
+
*
|
|
8
|
+
* The paths are the ones `mwc-enquesta-bretxa-digital` built its survey
|
|
9
|
+
* avatars from (`paths.json`). They are closed sets: extending one is a
|
|
10
|
+
* package change, so every consumer's `ArmsPose` stays honest (the `Icon`
|
|
11
|
+
* rule).
|
|
12
|
+
*/
|
|
13
|
+
/** Shoulder line and two hanging arms, in the survey's 153×111 torso box. */
|
|
14
|
+
export const ARMS = {
|
|
15
|
+
down: 'M27 107L39.375 8H113.625L126 107',
|
|
16
|
+
raised: 'M7 25L32.9376 57.5L39.125 8H113.375L119.563 57.5L145.5 25',
|
|
17
|
+
'one-bent': 'M51.899 91.3976L19 53.7671L38.9562 8H113.565L126 107'
|
|
18
|
+
};
|
|
19
|
+
/** Two legs in the survey's 91×187 box (its adult set). */
|
|
20
|
+
export const LEGS = {
|
|
21
|
+
standing: 'M21 7V179H8M70 7V179H82',
|
|
22
|
+
walking: 'M69 7L85 129.857L69 179H81.4218M21 7L36 129.857L21 179H32.6453',
|
|
23
|
+
stride: 'M21 7V80.7143L38 179H25.0024M71 7V80.7143L54 179H65.3333',
|
|
24
|
+
step: 'M21 7V179H8M70 7L85 129.857L70 179H81.6545',
|
|
25
|
+
kneel: 'M21 7V179H9M71 7V130.087L46 168.589V182',
|
|
26
|
+
sit: 'M21 7V130.087L45 168.589V182M69 7L54 129.857L69 179H57.3546'
|
|
27
|
+
};
|
|
28
|
+
/** Drawn head outlines in the survey's 49×48 box, for a figure with no photo. */
|
|
29
|
+
export const HEADS = {
|
|
30
|
+
round: 'M25 41C33.8366 41 41 33.6127 41 24.5C41 15.3873 33.8366 8 25 8C16.1634 8 9 15.3873 9 24.5C9 33.6127 16.1634 41 25 41Z',
|
|
31
|
+
cup: 'M8 8V24.4952C7.99874 26.6662 8.4255 28.8161 9.25592 30.8216C10.0863 32.8271 11.3042 34.6488 12.8393 36.1822C14.3745 37.7155 16.1968 38.9304 18.2019 39.7571C20.207 40.5839 22.3554 41.0062 24.5239 40.9999C28.8936 40.9999 33.0843 39.262 36.1742 36.1686C39.2641 33.0751 41 28.8795 41 24.5047V8.00955L8 8Z',
|
|
32
|
+
d: 'M27.571 8H38V41H27.571C23.1726 40.9949 18.9563 39.2535 15.8489 36.1585C12.7415 33.0636 10.9975 28.8684 11 24.4952C11 20.1237 12.7453 15.931 15.8524 12.8381C18.9595 9.74519 23.1743 8.00507 27.571 8V8Z'
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The composed box. The survey stacked three svgs on a grid (rows 48 / 111 /
|
|
36
|
+
* 100, the legs' 187 hanging out of the last row); this is that stack
|
|
37
|
+
* flattened into one viewBox, plus 20 of headroom so a photo can rise above
|
|
38
|
+
* where the drawn head would be. Landmarks: shoulder line y 76 from x 39 to
|
|
39
|
+
* 114, legs from y 99, feet at y 271.
|
|
40
|
+
*/
|
|
41
|
+
export const FIGURE_WIDTH = 153;
|
|
42
|
+
export const FIGURE_HEIGHT = 280;
|
|
43
|
+
export const FIGURE_VIEWBOX = `0 0 ${FIGURE_WIDTH} ${FIGURE_HEIGHT}`;
|
|
44
|
+
export const HEAD_AT = 'translate(52 20)';
|
|
45
|
+
export const ARMS_AT = 'translate(0 68)';
|
|
46
|
+
export const LEGS_AT = 'translate(31 92)';
|
|
47
|
+
/** The neck: a photo's chin lands here, and `headScale` scales about it. */
|
|
48
|
+
export const NECK = { x: 76.5, y: 84 };
|
|
49
|
+
/** Where the callout leaves the body — the ends of the shoulder line. */
|
|
50
|
+
export const SHOULDER = { y: 76, left: 39.5, right: 113.5 };
|
|
51
|
+
/**
|
|
52
|
+
* The height of the label's rule: beside the head, or beside the legs.
|
|
53
|
+
* `PersonFigure` sizes the caption's grid row to it, so the caption's
|
|
54
|
+
* baseline meets the rule whatever the font size.
|
|
55
|
+
*/
|
|
56
|
+
export const RULE_Y = { top: 50, bottom: 200 };
|
|
57
|
+
/** How far the callout runs out of the art before the rule, and the rule's length. */
|
|
58
|
+
export const CALLOUT_RUN = 24;
|
|
59
|
+
export const CALLOUT_RULE = 90;
|
|
60
|
+
/**
|
|
61
|
+
* Shoulder → the rule's inner end → along the rule. Mirrored for a label on
|
|
62
|
+
* the left, where it runs into negative x — the art's svg draws with
|
|
63
|
+
* `overflow: visible` for exactly this.
|
|
64
|
+
*/
|
|
65
|
+
export function calloutPath(side, align) {
|
|
66
|
+
const y = RULE_Y[align];
|
|
67
|
+
if (side === 'right') {
|
|
68
|
+
const inner = FIGURE_WIDTH + CALLOUT_RUN;
|
|
69
|
+
return `M${SHOULDER.right} ${SHOULDER.y}L${inner} ${y}H${inner + CALLOUT_RULE}`;
|
|
70
|
+
}
|
|
71
|
+
const inner = -CALLOUT_RUN;
|
|
72
|
+
return `M${SHOULDER.left} ${SHOULDER.y}L${inner} ${y}H${inner - CALLOUT_RULE}`;
|
|
73
|
+
}
|
package/dist/primitives.d.ts
CHANGED
|
@@ -15,6 +15,11 @@ export { default as IconButton } from './components/ui/IconButton.svelte';
|
|
|
15
15
|
export { default as Link } from './components/ui/Link.svelte';
|
|
16
16
|
export { default as Logo } from './components/ui/Logo.svelte';
|
|
17
17
|
export { default as Modal } from './components/ui/Modal.svelte';
|
|
18
|
+
export { default as FigureBody } from './components/ui/figure/FigureBody.svelte';
|
|
19
|
+
export { default as FigureHead } from './components/ui/figure/FigureHead.svelte';
|
|
20
|
+
export { default as PersonFigure } from './components/ui/figure/PersonFigure.svelte';
|
|
21
|
+
export { ARMS, LEGS, HEADS, NECK, SHOULDER, FIGURE_VIEWBOX, calloutPath } from './components/ui/figure/paths.js';
|
|
22
|
+
export type { ArmsPose, HeadShape, LabelAlign, LabelSide, LegsPose } from './components/ui/figure/paths.js';
|
|
18
23
|
export { default as Pagination } from './components/ui/Pagination.svelte';
|
|
19
24
|
export { default as RichText } from './components/ui/RichText.svelte';
|
|
20
25
|
export { default as SearchInput } from './components/ui/SearchInput.svelte';
|
package/dist/primitives.js
CHANGED
|
@@ -15,6 +15,12 @@ export { default as IconButton } from './components/ui/IconButton.svelte';
|
|
|
15
15
|
export { default as Link } from './components/ui/Link.svelte';
|
|
16
16
|
export { default as Logo } from './components/ui/Logo.svelte';
|
|
17
17
|
export { default as Modal } from './components/ui/Modal.svelte';
|
|
18
|
+
// The figure, in parts: the drawing and the callout arithmetic, the line
|
|
19
|
+
// body, the head's three modes, and the person that composes them.
|
|
20
|
+
export { default as FigureBody } from './components/ui/figure/FigureBody.svelte';
|
|
21
|
+
export { default as FigureHead } from './components/ui/figure/FigureHead.svelte';
|
|
22
|
+
export { default as PersonFigure } from './components/ui/figure/PersonFigure.svelte';
|
|
23
|
+
export { ARMS, LEGS, HEADS, NECK, SHOULDER, FIGURE_VIEWBOX, calloutPath } from './components/ui/figure/paths.js';
|
|
18
24
|
export { default as Pagination } from './components/ui/Pagination.svelte';
|
|
19
25
|
export { default as RichText } from './components/ui/RichText.svelte';
|
|
20
26
|
export { default as SearchInput } from './components/ui/SearchInput.svelte';
|
package/dist/styles/tokens.css
CHANGED
|
@@ -115,4 +115,16 @@
|
|
|
115
115
|
--vit-madlib-menu-shadow: none;
|
|
116
116
|
--vit-madlib-menu-max-height: 15rem;
|
|
117
117
|
--vit-madlib-menu-z: 40;
|
|
118
|
+
|
|
119
|
+
/* Person figure (`PersonFigure`, on `./primitives`) */
|
|
120
|
+
/* The drawing's width; everything in the figure — body, callout run, the
|
|
121
|
+
rule's height above the baseline — derives from it. Text stays in rem. */
|
|
122
|
+
--vit-figure-width: 10rem;
|
|
123
|
+
--vit-figure-ink: var(--color-ink);
|
|
124
|
+
/* Body stroke in viewBox units (153 across), so it scales with the width. */
|
|
125
|
+
--vit-figure-stroke: 4;
|
|
126
|
+
--vit-figure-callout-ink: var(--color-ink);
|
|
127
|
+
--vit-figure-name-size: var(--text-base);
|
|
128
|
+
--vit-figure-role-size: var(--text-sm);
|
|
129
|
+
--vit-figure-bio-width: 14rem;
|
|
118
130
|
}
|