@crewlethq/ui 0.19.0 → 0.20.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 +22 -3
- package/dist/CommentThread/CommentThread.d.ts +85 -0
- package/dist/CommentThread/index.d.ts +3 -0
- package/dist/CommentThread/index.js +16 -0
- package/dist/CommentThread-7VXOFTZO.css +90 -0
- package/dist/ConflictNote/ConflictNote.d.ts +42 -0
- package/dist/ConflictNote/index.d.ts +3 -0
- package/dist/ConflictNote/index.js +4 -0
- package/dist/ConflictNote-DH77PYF7.css +27 -0
- package/dist/DiffView/DiffView.d.ts +59 -0
- package/dist/DiffView/diff.d.ts +97 -0
- package/dist/DiffView/index.d.ts +5 -0
- package/dist/DiffView/index.js +3 -0
- package/dist/DiffView-RVLIHTKG.css +91 -0
- package/dist/LinkInterstitial/index.js +2 -2
- package/dist/PeopleRail/PeopleRail.d.ts +52 -0
- package/dist/PeopleRail/index.d.ts +3 -0
- package/dist/PeopleRail/index.js +11 -0
- package/dist/PeopleRail-R64WVOSH.css +75 -0
- package/dist/SheetGrid/index.js +3 -3
- package/dist/TableCells/TableCells.d.ts +153 -0
- package/dist/TableCells/index.d.ts +3 -0
- package/dist/TableCells/index.js +10 -0
- package/dist/TableCells-3VN5N2M3.css +104 -0
- package/dist/VersionCompare/VersionCompare.d.ts +70 -0
- package/dist/VersionCompare/index.d.ts +3 -0
- package/dist/VersionCompare/index.js +11 -0
- package/dist/VersionCompare-UUZOPTQO.css +68 -0
- package/dist/chunk-73QBUUYX.js +102 -0
- package/dist/chunk-BVWSLXTY.js +187 -0
- package/dist/chunk-EZBLOUWX.js +176 -0
- package/dist/chunk-MUGM7SEG.js +88 -0
- package/dist/chunk-N4GFRW5C.js +96 -0
- package/dist/chunk-UO7JZVCO.js +35 -0
- package/dist/generated/index.d.ts +6 -0
- package/dist/generated/index.js +46 -40
- package/dist/index.js +46 -40
- package/dist/styles.css +473 -0
- package/package.json +3 -3
- package/dist/{chunk-RT323I4I.js → chunk-A4VMJMXJ.js} +1 -1
- package/dist/{chunk-PUQY4WXR.js → chunk-J2G4HGQM.js} +1 -1
- /package/dist/{chunk-4Z7PW3L6.js → chunk-MSSZHDZI.js} +0 -0
package/README.md
CHANGED
|
@@ -43,9 +43,10 @@ import { Button } from '@crewlethq/ui/Button'; // 8 KB of CSS, not 182 KB
|
|
|
43
43
|
| Actions | `Button` and `ButtonLink` (variants `primary`, `secondary`, `outline`, `ghost`, `accent`, `danger`; sizes `small`, `medium`, `large`; shapes `square`, `pill`), `IconButton`, `Menu`, `Link`, `Copyable`, `CopyButton`, `DownloadButton`, `Kbd` |
|
|
44
44
|
| Content and text | `Text` (the eight registers and `inherit`), `Prose`, `Document` and `DocumentTitle`, `Highlight`, `InlineCode`, `List` and `ListItem`, `DescriptionList`, `Timeline`, `RelativeTime`, `Note`, `Divider` |
|
|
45
45
|
| Forms | `Input`, `Textarea`, `Label`, `FormField`, `FormRow`, `Checkbox`, `CheckboxGroup`, `ConfirmField`, `Select`, `Tag`, `TagsInput`, `DateTimePicker`, `TimeWindowPicker`, `ImageUpload`, `CharacterPicker`, `HuePicker`, `AvatarPicker`, `FactList`, `ChoiceList`, `Composer`, `MarkdownEditor` |
|
|
46
|
-
| Data display | `DataTable`, `CopyableCell`, `Table`, `StatCard`, `Meter`, `SegmentedMeter`, `Stepper`, `PricingCard`, `Avatar`, `AvatarStack`, `EntityChip`, `CodeBlock`, `QrCode`, `ObjectHeader`, `FactLine`, `SetByLine`, `Skeleton`, `Eyebrow`, `StatGroup`, `PinTile`, `IconTile` |
|
|
46
|
+
| Data display | `DataTable`, `CopyableCell`, `Table`, the read-only table cells (`TableKeyCell`, `TableNumberCell`, `TableDateCell`, `TableEntityCell`, `TableStatusCell`, `TableTagsCell`, `TableTextCell`), `StatCard`, `Meter`, `SegmentedMeter`, `Stepper`, `PricingCard`, `Avatar`, `AvatarStack`, `EntityChip`, `CodeBlock`, `QrCode`, `ObjectHeader`, `FactLine`, `SetByLine`, `Skeleton`, `Eyebrow`, `StatGroup`, `PinTile`, `IconTile` |
|
|
47
47
|
| List screens | `DataView`, and its parts on their own: `DataViewToolbar`, `FilterAxisBar`, `FilterAxisChip`, plus the filter model (`filterDefsFromColumns`, `applyColumnFilters`, `filterPredicate`, `blankFilterValue`) |
|
|
48
|
-
| Feedback and overlays | `Callout`, `Toaster`, `Modal` (with `variant="sheet"`), `ConfirmModal`, `CommandPalette`, `Tooltip`, `Popover`, `Announcer` |
|
|
48
|
+
| Feedback and overlays | `Callout`, `ConflictNote`, `Toaster`, `Modal` (with `variant="sheet"`), `ConfirmModal`, `CommandPalette`, `Tooltip`, `Popover`, `Announcer` |
|
|
49
|
+
| Documents and review | `DiffView` and `DiffSummary` with the line diff they draw (`diffLines`, `diffStat`, `collapseDiff`, `firstChangedLines`, `splitLines`, `MAX_DIFF_LINES`, with no React in it), `DiffList`, `VersionCompare`, `CommentThread` |
|
|
49
50
|
| Charts | `Legend`, `BarList`, `StackedBar`, `StackedColumns`, `ActivityStrip`, `TimeSeries`, `Sparkline`, the reading layer they share (`ChartTooltip`, `useChartHover`), and the ramp (`DATA_COLORS`, `DATA_COLOR_OTHER`, `dataColor`) |
|
|
50
51
|
| Marks | `Count`, `StatusDot`, `EmptyValue`, `VisuallyHidden` |
|
|
51
52
|
| Hooks and seams | `useModalLayer`, `usePopupLayer`, `LayerHost`, `useListbox`, `useOptionKeys`, `useHighlightRest` and `HIGHLIGHT_REST_MS`, `useClipboard`, `useDownload`, `downloadText`, `safeFilename`, `useOutcome`, `useAnnouncer`, `useNow`, `formatRelative`, `HeadingLevelProvider`, `cx`, the node hues (`NODE_HUES`, `NODE_HUE_NAMES`, `isNodeHue`) |
|
|
@@ -55,7 +56,7 @@ import { Button } from '@crewlethq/ui/Button'; // 8 KB of CSS, not 182 KB
|
|
|
55
56
|
| Preferences | `ThemeToggle`, `ThemeSwitcher`, `DensitySwitcher`, `ThemeGallery`, `ThemeSwatch`, `useThemePreference`, `useDensityPreference`, `useSystemTheme`, `resolveTheme`, `applyStoredPreferences`, and the framework-free `applyTheme`, `applyPalette`, `applyDensity` with `THEME_PREFERENCES`, `DENSITY_PREFERENCES` and their guards |
|
|
56
57
|
| Artifacts | `SandboxedFrame`, `LinkInterstitial` and `openExternal`, `VersionTimeline`, `ShareDialog` with the role ladder (`ARTIFACT_ROLES`, `SHAREABLE_ROLES`, `roleAtLeast`), `PrincipalPicker`, `Principal` and `PrincipalAvatar`, `ArtifactCard` and `ArtifactKindGlyph`, `AccessDeniedCard`, and `FrameProtocol` (the wire between a host page and an embedded artifact, with no React in it) |
|
|
57
58
|
| Sheets | `SheetGrid`, the cells on their own (`CheckboxCell`, `TextCell`, `LongTextCell`, `NumberCell`, `SelectCell`, `MultiSelectCell`, `PersonCell`, `StatusCell`, `DateCell`, `UrlCell`, `RatingCell`, and `SheetCell` for any type), `ChangeMark`, and `SheetModel` (every rule a sheet is drawn from, with no React in it: values, parsing, the keyboard model, TSV, the pending edits of D23) |
|
|
58
|
-
| Presence | `PresenceStack`, `PresenceRing` and `presenceHueClass`, `CursorFlag`, `LiveCursor`, `FollowBanner`, and `PresenceModel` (the roster order, one entry per person, the session hues, with no React in it) |
|
|
59
|
+
| Presence | `PresenceStack`, `PresenceRing` and `presenceHueClass`, `CursorFlag`, `LiveCursor`, `FollowBanner`, `PeopleRail` (with `peopleRailKey`), and `PresenceModel` (the roster order, one entry per person, the session hues, with no React in it) |
|
|
59
60
|
|
|
60
61
|
Storybook stories live under `apps/storybook/src/stories`.
|
|
61
62
|
|
|
@@ -202,6 +203,24 @@ places:
|
|
|
202
203
|
- A surface that pans or zooms dispatches `LAYER_REPOSITION_EVENT` on its host
|
|
203
204
|
when the content beneath it moves, and every panel anchored inside follows.
|
|
204
205
|
|
|
206
|
+
## Changes in 0.20.0
|
|
207
|
+
|
|
208
|
+
0.20.0 adds the review surfaces the engine dashboard drew for itself: a line diff, two versions compared, a document's comment thread, the note a lost edit race leaves, the People rail beside a shared document, and the read-only cells its tables share. Each takes its data and its callbacks as props, so nothing in it fetches or knows a route.
|
|
209
|
+
|
|
210
|
+
**`DiffView`** draws a line diff as the document it is: monospace, numbered on both sides, a row that says how many unchanged lines a gap stands for, and on every changed line a tint, a mark and the word "Added" or "Removed" read in the mark's place. **`DiffSummary`** is the line above it, `+3 −1 against version 4`, read as a sentence. The diff itself is pure and exported with them: `diffLines` (the longest common subsequence over lines, a removal above its addition, a whole replacement past `MAX_DIFF_LINES`), `diffStat`, `collapseDiff` (context either side of each change, the gap counted), `firstChangedLines` (a preview with the count of what it left out) and `splitLines`.
|
|
211
|
+
|
|
212
|
+
**`VersionCompare`** is two versions of a document compared: the changes lens draws a `DiffView` of two texts with its summary, or a comparison the host draws in `changes` (a `DiffList` of files, say), and the versions lens draws the two side by side, older first, stacking where the panes are narrow. Two identical texts are said in words, a lens with nothing to draw is not offered, and `labels` renames the lenses ("Full text").
|
|
213
|
+
|
|
214
|
+
**`CommentThread`** is a document's remarks in order, each with its author (`Principal`), when it was written and edited, and whom it answers, then a `Composer` to add one. Reply moves focus to the field, which names whom it answers. The post is one at a time and the draft is cleared only once `onSubmit` says it landed; a standing `disabledReason` is said in the open. Without `onSubmit` the thread is read-only.
|
|
215
|
+
|
|
216
|
+
**`ConflictNote`** is the refusal of a lost race, an alert that names whoever changed the record (`changedBy`) once the host knows, the server's sentence until then, with Dismiss and any controls of the host's.
|
|
217
|
+
|
|
218
|
+
**`PeopleRail`** lists everybody present in words, one entry a seat, in the order `PresenceStack` draws them, each with what they are doing and where, Follow as a toggle that names whom it follows, and a slot for the host's own controls ("Open turn"). **`peopleRailKey`** is the key `following` is compared with.
|
|
219
|
+
|
|
220
|
+
**The read-only table cells** draw one kind of value the same in every table: `TableKeyCell` (an identifier on one line, its whole value on the title, optionally a link), `TableNumberCell` (tabular, the host's formatter, absent never drawn as zero), `TableDateCell` (relative, exact on hover), `TableEntityCell` (a badge and a name, optionally a link), `TableStatusCell` (the caller's mark with its word and tone), `TableTagsCell` (capped, the rest counted in words) and `TableTextCell` (a column's decorative icon or a row's own mark). They are prefixed `Table` because `SheetGrid`'s editable cells own the bare names.
|
|
221
|
+
|
|
222
|
+
What to change: nothing. Every addition is new.
|
|
223
|
+
|
|
205
224
|
## Changes in 0.19.0
|
|
206
225
|
|
|
207
226
|
0.19.0 adds what a page needs to be arranged by its reader: blocks they move, resize and group on a board, the layout pieces they add between them, and the tray those come from. The engine console's Home is built from it.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type Principal } from '../Principal/index.js';
|
|
3
|
+
import { type TimeValue } from '../RelativeTime/index.js';
|
|
4
|
+
/** One remark in a thread. */
|
|
5
|
+
export interface CommentThreadComment {
|
|
6
|
+
/** Stable across edits: what a reply points at. */
|
|
7
|
+
id: string;
|
|
8
|
+
/** Who wrote it. Absent, the remark is the system's, named by `labels.system`. */
|
|
9
|
+
author?: Principal | null | undefined;
|
|
10
|
+
/** The remark, already rendered by the host: markdown turned into elements, or plain text. */
|
|
11
|
+
body: ReactNode;
|
|
12
|
+
createdAt: TimeValue;
|
|
13
|
+
/** When it was last edited. "(edited)" is shown when it differs from `createdAt`. */
|
|
14
|
+
editedAt?: TimeValue | undefined;
|
|
15
|
+
/** The id of the remark this one answers. */
|
|
16
|
+
replyTo?: string | undefined;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* What a submit answers. `false` keeps the draft and the reply target, for a
|
|
20
|
+
* write the server refused; anything else clears them.
|
|
21
|
+
*/
|
|
22
|
+
export type CommentThreadSubmitResult = boolean | void;
|
|
23
|
+
export interface CommentThreadLabels {
|
|
24
|
+
heading: string;
|
|
25
|
+
empty: string;
|
|
26
|
+
/** The author of a remark that names nobody. */
|
|
27
|
+
system: string;
|
|
28
|
+
edited: string;
|
|
29
|
+
inReplyTo: (name: string) => string;
|
|
30
|
+
/** The Reply control's accessible name, which names whose remark it answers. */
|
|
31
|
+
replyTo: (name: string) => string;
|
|
32
|
+
reply: string;
|
|
33
|
+
replying: (name: string) => string;
|
|
34
|
+
stopReplying: string;
|
|
35
|
+
/** The field's accessible name. `subject` is the document commented on, when the host names one. */
|
|
36
|
+
field: (subject: string | undefined, replyingTo: string | undefined) => string;
|
|
37
|
+
placeholder: string;
|
|
38
|
+
submit: string;
|
|
39
|
+
submitReply: string;
|
|
40
|
+
/** Why the submit control waits while the field is empty. */
|
|
41
|
+
emptyDraft: string;
|
|
42
|
+
}
|
|
43
|
+
export declare const COMMENT_THREAD_LABELS: CommentThreadLabels;
|
|
44
|
+
export interface CommentThreadProps {
|
|
45
|
+
comments: readonly CommentThreadComment[];
|
|
46
|
+
/** The document commented on, named in the field's label: Comment on "Onboarding". */
|
|
47
|
+
subject?: string | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Posts a remark, or a reply when `replyTo` is set. Without it the thread is
|
|
50
|
+
* read-only: no field and no Reply controls. Resolve to `false` to keep the
|
|
51
|
+
* draft, such as when the server refused the write.
|
|
52
|
+
*/
|
|
53
|
+
onSubmit?: ((body: string, replyTo: CommentThreadComment | null) => CommentThreadSubmitResult | Promise<CommentThreadSubmitResult>) | undefined;
|
|
54
|
+
/** Why the reader cannot post right now. The submit control is disabled and the reason is shown under it. */
|
|
55
|
+
disabledReason?: string | undefined;
|
|
56
|
+
/** A post in flight that the host is tracking. The thread also tracks its own. */
|
|
57
|
+
busy?: boolean | undefined;
|
|
58
|
+
/** A line beside the submit control: who is told, how the remark is shared. */
|
|
59
|
+
hint?: ReactNode;
|
|
60
|
+
/** Under the field: the server's refusal of the last post, as the host words it. */
|
|
61
|
+
notice?: ReactNode;
|
|
62
|
+
/** Whether the thread draws its own heading. On by default; off where the host already titles the section. */
|
|
63
|
+
heading?: boolean | undefined;
|
|
64
|
+
/** Overrides the shared clock, for a story or a suite. */
|
|
65
|
+
now?: number | undefined;
|
|
66
|
+
labels?: Partial<CommentThreadLabels> | undefined;
|
|
67
|
+
className?: string | undefined;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A document's comments, oldest first, and a field to add one.
|
|
71
|
+
*
|
|
72
|
+
* A REPLY NAMES WHAT IT ANSWERS in words ("in reply to Maya"), so the thread
|
|
73
|
+
* stays flat and readable in order while each answer still points at its
|
|
74
|
+
* question. Pressing Reply moves focus into the field, which names whom it
|
|
75
|
+
* answers, and the reply can be abandoned from beside it.
|
|
76
|
+
*
|
|
77
|
+
* THE BODY IS THE HOST'S. A remark arrives already rendered (markdown into
|
|
78
|
+
* elements), so this draws it in the kit's document type at the size a remark
|
|
79
|
+
* read beside other things takes, and never parses anything itself.
|
|
80
|
+
*
|
|
81
|
+
* A SUBMIT IS ONE AT A TIME. The submit control is disabled while a post is in
|
|
82
|
+
* flight, so a second press cannot send the same remark twice, and the draft
|
|
83
|
+
* is cleared only once the host says the post landed.
|
|
84
|
+
*/
|
|
85
|
+
export declare function CommentThread({ comments, subject, onSubmit, disabledReason, busy, hint, notice, heading, now, labels: overrides, className, }: CommentThreadProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { COMMENT_THREAD_LABELS, CommentThread } from '../chunk-EZBLOUWX.js';
|
|
2
|
+
import '../chunk-WI6K5M5X.js';
|
|
3
|
+
import '../chunk-VJLYGPPE.js';
|
|
4
|
+
import '../chunk-SMBKXWML.js';
|
|
5
|
+
import '../chunk-OG6CL3EB.js';
|
|
6
|
+
import '../chunk-Q55TXPA3.js';
|
|
7
|
+
import '../chunk-4KP4RC3O.js';
|
|
8
|
+
import '../chunk-3CZ7XAVY.js';
|
|
9
|
+
import '../chunk-EUCC37WY.js';
|
|
10
|
+
import '../chunk-UAN5NE7M.js';
|
|
11
|
+
import '../chunk-5OM447RW.js';
|
|
12
|
+
import '../chunk-PQADV6P6.js';
|
|
13
|
+
import '../chunk-BHHIIK3M.js';
|
|
14
|
+
import '../chunk-4FQT6ZAY.js';
|
|
15
|
+
import '../chunk-BZFWDIV6.js';
|
|
16
|
+
import '../chunk-ODEQTPFM.js';
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/* CommentThread: a document's remarks, one per row with a hairline between,
|
|
2
|
+
then the field to add one. */
|
|
3
|
+
.crewlet-comment-thread {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
gap: var(--spacing-3);
|
|
7
|
+
min-width: 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.crewlet-comment-thread__heading {
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
gap: var(--spacing-2);
|
|
14
|
+
margin: 0;
|
|
15
|
+
color: var(--color-text-primary);
|
|
16
|
+
font-size: var(--font-size-sm);
|
|
17
|
+
font-weight: var(--font-weight-semibold);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.crewlet-comment-thread__empty,
|
|
21
|
+
.crewlet-comment-thread__hint {
|
|
22
|
+
margin: 0;
|
|
23
|
+
color: var(--color-text-tertiary);
|
|
24
|
+
font-size: var(--font-size-compact);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.crewlet-comment-thread__list {
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
margin: 0;
|
|
31
|
+
padding: 0;
|
|
32
|
+
list-style: none;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.crewlet-comment-thread__comment {
|
|
36
|
+
display: flex;
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
gap: var(--spacing-2);
|
|
39
|
+
min-width: 0;
|
|
40
|
+
padding: var(--spacing-3) 0;
|
|
41
|
+
border-bottom: 1px solid var(--color-border-default);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.crewlet-comment-thread__head {
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-wrap: wrap;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: var(--spacing-2);
|
|
49
|
+
min-width: 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.crewlet-comment-thread__author {
|
|
53
|
+
display: inline-flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
gap: var(--spacing-2);
|
|
56
|
+
min-width: 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.crewlet-comment-thread__name {
|
|
60
|
+
overflow: hidden;
|
|
61
|
+
color: var(--color-text-primary);
|
|
62
|
+
font-weight: var(--font-weight-semibold);
|
|
63
|
+
text-overflow: ellipsis;
|
|
64
|
+
white-space: nowrap;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.crewlet-comment-thread__meta {
|
|
68
|
+
color: var(--color-text-tertiary);
|
|
69
|
+
font-size: var(--font-size-xs);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Reply sits at the row's far end, wherever the head wraps. */
|
|
73
|
+
.crewlet-comment-thread__reply {
|
|
74
|
+
margin-inline-start: auto;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.crewlet-comment-thread__compose {
|
|
78
|
+
display: flex;
|
|
79
|
+
flex-direction: column;
|
|
80
|
+
gap: var(--spacing-2);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.crewlet-comment-thread__replying {
|
|
84
|
+
display: flex;
|
|
85
|
+
flex-wrap: wrap;
|
|
86
|
+
align-items: center;
|
|
87
|
+
gap: var(--spacing-2);
|
|
88
|
+
color: var(--color-text-tertiary);
|
|
89
|
+
font-size: var(--font-size-xs);
|
|
90
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
export interface ConflictNoteLabels {
|
|
3
|
+
/** The sentence once the host knows who won the race. */
|
|
4
|
+
changedBy: (subject: string, name: string) => string;
|
|
5
|
+
/** The sentence while it does not, or when nobody can be named. */
|
|
6
|
+
changed: (subject: string) => string;
|
|
7
|
+
dismiss: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const CONFLICT_NOTE_LABELS: ConflictNoteLabels;
|
|
10
|
+
export interface ConflictNoteProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'role'> {
|
|
11
|
+
/** What the sentence calls the record the reader tried to change: a key, a title. */
|
|
12
|
+
subject: string;
|
|
13
|
+
/**
|
|
14
|
+
* Who changed it in the meantime, once the host has looked it up. Without
|
|
15
|
+
* it the note says the record changed and names nobody, or says `message`.
|
|
16
|
+
*/
|
|
17
|
+
changedBy?: string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* The sentence to say while nobody can be named, in place of the default:
|
|
20
|
+
* usually the server's own refusal.
|
|
21
|
+
*/
|
|
22
|
+
message?: ReactNode;
|
|
23
|
+
/** Takes the note down. Without it the note offers no Dismiss control. */
|
|
24
|
+
onDismiss?: (() => void) | undefined;
|
|
25
|
+
/** Controls beside Dismiss: "Reload", "Keep mine". */
|
|
26
|
+
actions?: ReactNode;
|
|
27
|
+
labels?: Partial<ConflictNoteLabels> | undefined;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The refusal of a lost race: somebody changed a record between the reader's
|
|
31
|
+
* read and their write, and the write was refused.
|
|
32
|
+
*
|
|
33
|
+
* IT NAMES WHO WON, when the host can say, because "changed by Maya since you
|
|
34
|
+
* opened it" names the person to talk to where "somebody else" names nobody.
|
|
35
|
+
* Until the host knows, it says the record changed, or the server's own
|
|
36
|
+
* sentence.
|
|
37
|
+
*
|
|
38
|
+
* AN ALERT, because it answers a press the reader just made and they need to
|
|
39
|
+
* hear that the change did not land. The glyph is drawn beside the words so
|
|
40
|
+
* the danger ink is never the only carrier.
|
|
41
|
+
*/
|
|
42
|
+
export declare function ConflictNote({ subject, changedBy, message, onDismiss, actions, labels: overrides, className, ...rest }: ConflictNoteProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/* ConflictNote: a refused write's sentence in the danger ink, with its glyph
|
|
2
|
+
so the colour is never the only carrier, and its controls beside it. */
|
|
3
|
+
.crewlet-conflict-note {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-wrap: wrap;
|
|
6
|
+
align-items: center;
|
|
7
|
+
gap: var(--spacing-1) var(--spacing-2);
|
|
8
|
+
max-width: 40rem;
|
|
9
|
+
color: var(--color-feedback-danger-ink);
|
|
10
|
+
font-size: var(--font-size-compact);
|
|
11
|
+
overflow-wrap: anywhere;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.crewlet-conflict-note__glyph {
|
|
15
|
+
flex: none;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.crewlet-conflict-note__text {
|
|
19
|
+
flex: 1 1 16rem;
|
|
20
|
+
min-width: 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.crewlet-conflict-note__actions {
|
|
24
|
+
display: inline-flex;
|
|
25
|
+
flex-wrap: wrap;
|
|
26
|
+
gap: var(--spacing-1);
|
|
27
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react';
|
|
2
|
+
import type { DiffSection } from './diff.js';
|
|
3
|
+
export interface DiffViewLabels {
|
|
4
|
+
/** The row a dropped run of unchanged lines is drawn as: "12 unchanged lines". */
|
|
5
|
+
skipped: (count: number) => string;
|
|
6
|
+
/** Read in place of the `+` mark on an added line. */
|
|
7
|
+
added: string;
|
|
8
|
+
/** Read in place of the minus mark on a removed line. */
|
|
9
|
+
removed: string;
|
|
10
|
+
/** The region's accessible name when the caller gives none. */
|
|
11
|
+
region: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const DIFF_VIEW_LABELS: DiffViewLabels;
|
|
14
|
+
export interface DiffViewProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
15
|
+
/**
|
|
16
|
+
* The lines to draw, grouped into sections with the count of unchanged
|
|
17
|
+
* lines dropped before each: `collapseDiff(diffLines(before, after))`, or
|
|
18
|
+
* sections a host computed some other way.
|
|
19
|
+
*/
|
|
20
|
+
sections: readonly DiffSection[];
|
|
21
|
+
labels?: Partial<DiffViewLabels> | undefined;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A line diff, rendered as the document it is.
|
|
25
|
+
*
|
|
26
|
+
* MONOSPACE AND LINE-NUMBERED on both sides, because the two numbers are what
|
|
27
|
+
* a reader uses to find the paragraph in the version beside it. A skipped run
|
|
28
|
+
* is a row of its own saying how many lines it stands for: a gap silently
|
|
29
|
+
* closed makes a document edited at both ends look like one rewritten in the
|
|
30
|
+
* middle.
|
|
31
|
+
*
|
|
32
|
+
* COLOUR IS NEVER THE ONLY SIGNAL. An added or removed line carries a tint, a
|
|
33
|
+
* drawn mark and a word: the mark is hidden from assistive technology and the
|
|
34
|
+
* word ("Added", "Removed") is read in its place, because a minus sign is
|
|
35
|
+
* announced as anything from "minus" to nothing at all.
|
|
36
|
+
*/
|
|
37
|
+
export declare function DiffView({ sections, labels: overrides, className, 'aria-label': ariaLabel, ...rest }: DiffViewProps): import("react").JSX.Element;
|
|
38
|
+
export interface DiffSummaryLabels {
|
|
39
|
+
/** Read in place of the drawn counts: "3 lines added, 1 removed". */
|
|
40
|
+
spoken: (added: number, removed: number) => string;
|
|
41
|
+
/** What the counts are measured against: "against version 4". */
|
|
42
|
+
against: (what: string) => string;
|
|
43
|
+
}
|
|
44
|
+
export declare const DIFF_SUMMARY_LABELS: DiffSummaryLabels;
|
|
45
|
+
export interface DiffSummaryProps extends Omit<HTMLAttributes<HTMLParagraphElement>, 'children'> {
|
|
46
|
+
added: number;
|
|
47
|
+
removed: number;
|
|
48
|
+
/** What the diff is measured against, named the way the reader knows it: "version 4", "rev 12". */
|
|
49
|
+
against?: string | undefined;
|
|
50
|
+
labels?: Partial<DiffSummaryLabels> | undefined;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The one line above a diff that says how big it is: `+3 −1 against version 4`.
|
|
54
|
+
*
|
|
55
|
+
* The drawn counts are tinted with the same inks as the lines they count, and
|
|
56
|
+
* a sentence is read in their place, so the size of the change never rests on
|
|
57
|
+
* a plus sign and a colour.
|
|
58
|
+
*/
|
|
59
|
+
export declare function DiffSummary({ added, removed, against, labels: overrides, className, ...rest }: DiffSummaryProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What changed between two versions of a document, by line.
|
|
3
|
+
*
|
|
4
|
+
* OVER LINES, by the classic longest-common-subsequence table. The shape is
|
|
5
|
+
* the one a prose document wants: a paragraph somebody rewrote is a line
|
|
6
|
+
* replaced, not thirty character edits scattered through it. Word-level
|
|
7
|
+
* refinement inside a changed line is deliberately absent: it makes a small
|
|
8
|
+
* edit prettier and a large one unreadable, and documents are rewritten in
|
|
9
|
+
* paragraphs.
|
|
10
|
+
*
|
|
11
|
+
* THE TABLE RATHER THAN MYERS, and [MAX_DIFF_LINES] is what pays for that
|
|
12
|
+
* choice. The table is quadratic on every input, but it is a dozen lines
|
|
13
|
+
* anybody can check by reading; the cap keeps the quadratic term small, and a
|
|
14
|
+
* document past it is reported as a whole replacement rather than freezing the
|
|
15
|
+
* tab.
|
|
16
|
+
*
|
|
17
|
+
* FRAMEWORK FREE. Nothing here imports React, so a host can compute a diff in
|
|
18
|
+
* a worker or a test and hand the sections to [DiffView].
|
|
19
|
+
*/
|
|
20
|
+
/** One line of the result. `same` lines carry both numbers. */
|
|
21
|
+
export interface DiffLine {
|
|
22
|
+
kind: 'same' | 'add' | 'remove';
|
|
23
|
+
text: string;
|
|
24
|
+
/** 1-based line number in the OLD document, or null on an addition. */
|
|
25
|
+
before: number | null;
|
|
26
|
+
/** 1-based line number in the NEW document, or null on a removal. */
|
|
27
|
+
after: number | null;
|
|
28
|
+
}
|
|
29
|
+
/** What a diff amounts to, for a one-line summary above it. */
|
|
30
|
+
export interface DiffStat {
|
|
31
|
+
added: number;
|
|
32
|
+
removed: number;
|
|
33
|
+
/** True when the two documents are identical, which is a real outcome. */
|
|
34
|
+
identical: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A run of lines kept for display, and how many unchanged lines were dropped
|
|
38
|
+
* before it. The gap is a count rather than a marker line, so the renderer
|
|
39
|
+
* decides what a gap looks like.
|
|
40
|
+
*/
|
|
41
|
+
export interface DiffSection {
|
|
42
|
+
/** The lines of this section, in order. */
|
|
43
|
+
lines: DiffLine[];
|
|
44
|
+
/** How many unchanged lines were dropped BEFORE this section. */
|
|
45
|
+
skipped: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The size past which the table is not worth building.
|
|
49
|
+
*
|
|
50
|
+
* SIZED FROM THE TABLE'S MEMORY, because that is the quantity that binds and
|
|
51
|
+
* it is paid on every input: `(N+1)` rows of `(M+1)` `Uint32` entries, every
|
|
52
|
+
* cell filled. At two thousand lines that is about 16 MB and 60 ms, which a
|
|
53
|
+
* tab holds without noticing; at ten thousand it is hundreds of megabytes and
|
|
54
|
+
* seconds on the main thread. Raising it means replacing the table with Myers
|
|
55
|
+
* or Hirschberg first.
|
|
56
|
+
*/
|
|
57
|
+
export declare const MAX_DIFF_LINES = 2000;
|
|
58
|
+
/**
|
|
59
|
+
* Split a document into the lines a diff compares.
|
|
60
|
+
*
|
|
61
|
+
* A trailing newline is not a line. Every file that ends properly has one, so
|
|
62
|
+
* counting it would report an added last line on every document that gained
|
|
63
|
+
* one. Both line endings read as one.
|
|
64
|
+
*/
|
|
65
|
+
export declare function splitLines(text: string): string[];
|
|
66
|
+
/**
|
|
67
|
+
* The line diff of two documents: every line of the result in order, context
|
|
68
|
+
* included, because a document diff is read as a document rather than as a
|
|
69
|
+
* patch.
|
|
70
|
+
*/
|
|
71
|
+
export declare function diffLines(before: string, after: string): DiffLine[];
|
|
72
|
+
/**
|
|
73
|
+
* Count the additions and removals, and say when there are none: identical is
|
|
74
|
+
* its own answer, not "0 added, 0 removed".
|
|
75
|
+
*/
|
|
76
|
+
export declare function diffStat(diff: readonly DiffLine[]): DiffStat;
|
|
77
|
+
/**
|
|
78
|
+
* Drop the runs of unchanged lines a reader does not need, keeping `context`
|
|
79
|
+
* lines either side of every change.
|
|
80
|
+
*
|
|
81
|
+
* A HOLE IS REPORTED, never silently closed: each section carries how many
|
|
82
|
+
* lines were dropped before it, so a reader can tell a document with two edits
|
|
83
|
+
* at its ends from one rewritten in the middle.
|
|
84
|
+
*/
|
|
85
|
+
export declare function collapseDiff(diff: readonly DiffLine[], context?: number): DiffSection[];
|
|
86
|
+
/**
|
|
87
|
+
* The first `max` changed lines of a collapsed diff, and how many changed
|
|
88
|
+
* lines are left out: what a preview of a save shows, with the count that says
|
|
89
|
+
* it is one. An unchanged context line is drawn and not counted.
|
|
90
|
+
*
|
|
91
|
+
* A COUNT, never a quiet cut: a preview ending at its sixth line reads as a
|
|
92
|
+
* save that changed six lines.
|
|
93
|
+
*/
|
|
94
|
+
export declare function firstChangedLines(sections: readonly DiffSection[], max: number): {
|
|
95
|
+
sections: DiffSection[];
|
|
96
|
+
more: number;
|
|
97
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import './DiffView.css';
|
|
2
|
+
export { DiffView, DiffSummary, DIFF_VIEW_LABELS, DIFF_SUMMARY_LABELS } from './DiffView.js';
|
|
3
|
+
export type { DiffViewLabels, DiffViewProps, DiffSummaryLabels, DiffSummaryProps } from './DiffView.js';
|
|
4
|
+
export { MAX_DIFF_LINES, collapseDiff, diffLines, diffStat, firstChangedLines, splitLines } from './diff.js';
|
|
5
|
+
export type { DiffLine, DiffSection, DiffStat } from './diff.js';
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* DiffView: a line diff, monospace and line-numbered on both sides.
|
|
2
|
+
|
|
3
|
+
A changed line is marked three ways, a tint, a drawn mark and a word read
|
|
4
|
+
in its place, because colour alone is not a signal and a printed or
|
|
5
|
+
forced-colours render loses the tint entirely. The tint is the soft step of
|
|
6
|
+
the state and the mark the matching ink step, the pairing the palette suite
|
|
7
|
+
measures. */
|
|
8
|
+
.crewlet-diff-view {
|
|
9
|
+
overflow: hidden;
|
|
10
|
+
border: 1px solid var(--color-border-default);
|
|
11
|
+
border-radius: var(--radius-sm);
|
|
12
|
+
font-family: var(--font-family-mono);
|
|
13
|
+
font-size: var(--font-size-xs);
|
|
14
|
+
line-height: var(--font-line-height-normal);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.crewlet-diff-view__section + .crewlet-diff-view__section,
|
|
18
|
+
.crewlet-diff-view__skip + .crewlet-diff-view__line {
|
|
19
|
+
border-top: 1px solid var(--color-border-default);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* A GAP SAYS WHAT IT STANDS FOR. Silently closed, a document edited at both
|
|
23
|
+
ends looks like one rewritten in the middle. */
|
|
24
|
+
.crewlet-diff-view__skip {
|
|
25
|
+
padding: var(--spacing-1) var(--spacing-3);
|
|
26
|
+
background: var(--color-surface-elevated);
|
|
27
|
+
color: var(--color-text-tertiary);
|
|
28
|
+
font-family: var(--font-family-sans);
|
|
29
|
+
font-size: var(--font-size-xs);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.crewlet-diff-view__line {
|
|
33
|
+
display: grid;
|
|
34
|
+
grid-template-columns: 3.5ch 3.5ch 2ch minmax(0, 1fr);
|
|
35
|
+
gap: var(--spacing-2);
|
|
36
|
+
padding: calc(var(--spacing-1) / 4) var(--spacing-3) calc(var(--spacing-1) / 4) var(--spacing-2);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.crewlet-diff-view__number {
|
|
40
|
+
color: var(--color-text-tertiary);
|
|
41
|
+
text-align: right;
|
|
42
|
+
user-select: none;
|
|
43
|
+
font-variant-numeric: tabular-nums;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.crewlet-diff-view__mark {
|
|
47
|
+
text-align: center;
|
|
48
|
+
user-select: none;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.crewlet-diff-view__text {
|
|
52
|
+
white-space: pre-wrap;
|
|
53
|
+
overflow-wrap: anywhere;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.crewlet-diff-view__line--add {
|
|
57
|
+
background: var(--color-feedback-success-soft);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.crewlet-diff-view__line--add .crewlet-diff-view__mark {
|
|
61
|
+
color: var(--color-feedback-success-ink);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.crewlet-diff-view__line--remove {
|
|
65
|
+
background: var(--color-feedback-danger-soft);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.crewlet-diff-view__line--remove .crewlet-diff-view__mark {
|
|
69
|
+
color: var(--color-feedback-danger-ink);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* DiffSummary: the size of the change, in the same inks as its lines. */
|
|
73
|
+
.crewlet-diff-summary {
|
|
74
|
+
margin: 0;
|
|
75
|
+
color: var(--color-text-secondary);
|
|
76
|
+
font-size: var(--font-size-compact);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.crewlet-diff-summary__added,
|
|
80
|
+
.crewlet-diff-summary__removed {
|
|
81
|
+
font-family: var(--font-family-mono);
|
|
82
|
+
font-variant-numeric: tabular-nums;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.crewlet-diff-summary__added {
|
|
86
|
+
color: var(--color-feedback-success-ink);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.crewlet-diff-summary__removed {
|
|
90
|
+
color: var(--color-feedback-danger-ink);
|
|
91
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { LinkInterstitial, openExternal } from '../chunk-
|
|
2
|
-
import '../chunk-7IJARUIF.js';
|
|
1
|
+
export { LinkInterstitial, openExternal } from '../chunk-A4VMJMXJ.js';
|
|
3
2
|
import '../chunk-5CLW2CXT.js';
|
|
3
|
+
import '../chunk-7IJARUIF.js';
|
|
4
4
|
import '../chunk-SVSCLHHI.js';
|
|
5
5
|
import '../chunk-OG6CL3EB.js';
|
|
6
6
|
import '../chunk-PQADV6P6.js';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type PresenceLabels, type PresenceMember } from '../PresenceModel/index.js';
|
|
3
|
+
/** The reader, drawn first as "You". */
|
|
4
|
+
export type PeopleRailSelf = Pick<PresenceMember, 'name' | 'kind' | 'character' | 'hue' | 'src'>;
|
|
5
|
+
export interface PeopleRailLabels {
|
|
6
|
+
title: string;
|
|
7
|
+
close: string;
|
|
8
|
+
you: string;
|
|
9
|
+
nobody: string;
|
|
10
|
+
follow: string;
|
|
11
|
+
following: string;
|
|
12
|
+
/** The Follow control's accessible name, which names whom it follows. */
|
|
13
|
+
followName: (name: string) => string;
|
|
14
|
+
stopFollowingName: (name: string) => string;
|
|
15
|
+
/** The activity word under a name with no location: "Editing", "Reading". */
|
|
16
|
+
activity: PresenceLabels['activity'];
|
|
17
|
+
}
|
|
18
|
+
export declare const PEOPLE_RAIL_LABELS: PeopleRailLabels;
|
|
19
|
+
export interface PeopleRailProps {
|
|
20
|
+
/** Everybody present but the reader. Two tabs of one seat are listed once, the most active. */
|
|
21
|
+
members: readonly PresenceMember[];
|
|
22
|
+
/** The reader, listed first as "You". */
|
|
23
|
+
self?: PeopleRailSelf | undefined;
|
|
24
|
+
/** The key of the followed member: its `seat`, or its `id` where it has none. */
|
|
25
|
+
following?: string | undefined;
|
|
26
|
+
/** Follow a member, or `null` to stop. Without it the rail offers no Follow controls. */
|
|
27
|
+
onFollow?: ((member: PresenceMember | null) => void) | undefined;
|
|
28
|
+
/** Close the rail. Without it the rail draws no close control. */
|
|
29
|
+
onClose?: (() => void) | undefined;
|
|
30
|
+
/** Further controls for one member, before Follow: "Open turn" for an agent at work. */
|
|
31
|
+
actions?: ((member: PresenceMember) => ReactNode) | undefined;
|
|
32
|
+
labels?: Partial<PeopleRailLabels> | undefined;
|
|
33
|
+
className?: string | undefined;
|
|
34
|
+
}
|
|
35
|
+
/** What identifies a member for following: the seat, so a second tab of the same person stays followed. */
|
|
36
|
+
export declare function peopleRailKey(member: Pick<PresenceMember, 'id' | 'seat'>): string;
|
|
37
|
+
/**
|
|
38
|
+
* Who is in a shared document now, what each is doing and where, with Follow
|
|
39
|
+
* beside everybody but the reader.
|
|
40
|
+
*
|
|
41
|
+
* THE SAME FACTS AS THE PRESENCE STACK, IN WORDS. A ring, a cursor and a cell
|
|
42
|
+
* outline are decorative to assistive technology, so this list is where a
|
|
43
|
+
* screen reader user learns who is here and where. It is ordered the way the
|
|
44
|
+
* stack is (people editing, agents editing, people viewing, agents reading,
|
|
45
|
+
* anybody idle last), so the two never disagree about who comes first.
|
|
46
|
+
*
|
|
47
|
+
* FOLLOW IS A TOGGLE, pressed while following, and its name says whom it
|
|
48
|
+
* follows or stops following, because a column of buttons all called "Follow"
|
|
49
|
+
* is a column of identical controls to a screen reader. The strip that says
|
|
50
|
+
* the reader is following is `FollowBanner`, which the host draws.
|
|
51
|
+
*/
|
|
52
|
+
export declare function PeopleRail({ members, self, following, onFollow, onClose, actions, labels: overrides, className, }: PeopleRailProps): import("react").JSX.Element;
|