@reliability-design/web 1.4.3 → 1.4.5

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.
Files changed (48) hide show
  1. package/dist/components/rel-accordion/rel-accordion.ts +13 -1
  2. package/dist/components/rel-autocomplete/README.md +1 -0
  3. package/dist/components/rel-autocomplete/rel-autocomplete.ts +56 -1
  4. package/dist/components/rel-button/rel-button.ts +18 -2
  5. package/dist/components/rel-calendar/rel-calendar.ts +23 -1
  6. package/dist/components/rel-carousel/rel-carousel.ts +20 -0
  7. package/dist/components/rel-checkbox/README.md +69 -22
  8. package/dist/components/rel-checkbox/rel-checkbox.ts +119 -9
  9. package/dist/components/rel-chip/rel-chip.ts +29 -0
  10. package/dist/components/rel-date-picker/rel-date-picker.ts +156 -13
  11. package/dist/components/rel-drawer/rel-drawer.ts +22 -58
  12. package/dist/components/rel-menu/rel-menu.ts +124 -15
  13. package/dist/components/rel-menubar/rel-menubar.ts +221 -63
  14. package/dist/components/rel-nav-menu/rel-nav-menu.ts +39 -3
  15. package/dist/components/rel-otp/rel-otp.ts +3 -3
  16. package/dist/components/rel-pagination/README.md +14 -0
  17. package/dist/components/rel-pagination/rel-pagination.ts +30 -14
  18. package/dist/components/rel-popover/rel-popover.ts +31 -3
  19. package/dist/components/rel-range-picker/rel-range-picker.ts +251 -3
  20. package/dist/components/rel-resizable/rel-resizable-group.ts +20 -0
  21. package/dist/components/rel-segmented-button/rel-segmented-button.ts +50 -12
  22. package/dist/components/rel-select/README.md +1 -0
  23. package/dist/components/rel-select/rel-select.ts +1385 -1170
  24. package/dist/components/rel-sheet/rel-sheet.ts +22 -1
  25. package/dist/components/rel-sidebar/rel-sidebar.ts +43 -39
  26. package/dist/components/rel-stepper/rel-stepper.ts +44 -1
  27. package/dist/components/rel-tab/rel-tab.ts +32 -7
  28. package/dist/components/rel-time-picker/rel-time-picker.ts +338 -206
  29. package/dist/components/rel-tooltip/rel-tooltip.ts +30 -0
  30. package/dist/components/rel-topbar/rel-topbar-actions.ts +1 -1
  31. package/dist/components/rel-topbar/rel-topbar-brand.ts +1 -1
  32. package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +1 -1
  33. package/dist/components/rel-topbar/rel-topbar-dropdown.ts +1 -1
  34. package/dist/components/rel-topbar/rel-topbar-item.ts +318 -319
  35. package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +1 -1
  36. package/dist/components/rel-topbar/rel-topbar-profile.ts +224 -224
  37. package/dist/components/rel-topbar/rel-topbar-utilities.ts +1 -1
  38. package/dist/components/rel-topbar/rel-topbar.ts +440 -440
  39. package/dist/components/rel-upload/UPLOAD-DESIGN-CHANGES.md +134 -0
  40. package/dist/components/rel-upload/rel-upload-actions.ts +163 -0
  41. package/dist/components/rel-upload/rel-upload.ts +943 -0
  42. package/dist/index.d.mts +491 -18
  43. package/dist/index.d.ts +491 -18
  44. package/dist/index.js +2748 -410
  45. package/dist/index.js.map +1 -1
  46. package/dist/index.mjs +2953 -627
  47. package/dist/index.mjs.map +1 -1
  48. package/package.json +1 -1
@@ -0,0 +1,134 @@
1
+ # rel-upload — implementation vs. Figma, and what to update in the design
2
+
3
+ **For:** design team · **From:** engineering · **Date:** 2026-08-09
4
+ **Figma reference:** Reliability DS V1.0 → "↳ File Upload" page (node `8160:917`)
5
+ — `Upload Dropzone` (`8160:2615`), `Upload File Row` (`8160:2566`), `Upload Modal` (`8160:2622`)
6
+ **Shipped:** `<rel-upload>` + `<rel-upload-actions>` in `packages/web/src/components/rel-upload/`
7
+
8
+ This replaces the previous version of this doc, which was written against the
9
+ outdated sandbox design. Per our agreement, engineering adapted the design where
10
+ it had gaps or conflicted with how the design system already works — everything
11
+ that differs is listed here so the Figma file can be brought in line.
12
+
13
+ ---
14
+
15
+ ## What matches the Figma exactly
16
+
17
+ - **Dropzone**: 16px radius, dashed border, 32/24 padding, circular icon badge,
18
+ "Drag and drop your files here" (16/24 medium) over the format/size hint
19
+ (12/20), pill "Browse files" button.
20
+ - **File rows**: 8px radius, 12/16 padding, document icon → name (14/22) + meta
21
+ (12/20) → trailing status icon + remove. All four states (Ready, Uploading,
22
+ Success, Error) including the tinted error row.
23
+ - **Progress bar**: 4px, fully rounded, green fill, with the
24
+ `45% - 2.4 MB of 5.1 MB` meta line.
25
+ - **Modal**: composed from the existing `<rel-modal>` — title, supporting text,
26
+ close ×, scrolling body, pinned footer. All six variants are reproduced.
27
+ - **Copy**: every string in the design ships verbatim, including
28
+ `Selected files (3)`, `Uploading 3 files`, `3 files - 2 failed`,
29
+ `Upload 3 files`, `Retry upload`, and both alert messages.
30
+
31
+ The hint row is driven by real props: formats come from `accept`, the size limit
32
+ from `max-size`. The design's "PDF, PNG or JPG" / "10 MB" are example values.
33
+
34
+ ## Colour changes the design should adopt
35
+
36
+ All three are contrast or token-layer corrections. Every hex in the design
37
+ already exists in `vars.css`, but three of them are used at the wrong layer.
38
+
39
+ ### 1. Secondary text `#7D7D7D` → `--rel-text-caption` `#525252`
40
+
41
+ `#7D7D7D` (gray-400) scores **4.12:1 on white — below the 4.5:1 gate**, and in
42
+ the token system gray-400 is not a body-text token at all. Used for the dropzone
43
+ hint and the file-row meta line. `--rel-text-caption` (`#525252`) scores
44
+ **7.81:1**.
45
+
46
+ ### 2. Success text `#008655` → `--rel-text-success` `#006440`
47
+
48
+ Green-600 passes (4.62:1) but the system's success-text token is green-700
49
+ (**7.25:1**), and the green rule fixes success text/icons at `#006440`. Affects
50
+ the "…- Uploaded" meta line and the success check icon.
51
+
52
+ ### 3. Dashed dropzone border `#C2C2C2` → `--rel-border-accent` `#8C8C8C`
53
+
54
+ `#C2C2C2` scores **1.78:1**, under the 3:1 gate for UI boundaries, and gray-200
55
+ has no border token. `--rel-border-accent` (gray-350) scores **3.36:1**.
56
+
57
+ Also note: the error row border uses `--rel-border-danger` (`#CE3131`, 5.11:1)
58
+ rather than the design's `#FF5252` (3.19:1) — red-500 has no border token.
59
+ And the error meta on the tinted row (`#CE3131` on `#FFEBEB`) lands at
60
+ **4.46:1**, marginally under. That pairing is inherited from the existing
61
+ `rel-alert` subtle-error style, so it is a system-wide question rather than
62
+ something this component introduced — flagging it, not changing it here.
63
+
64
+ ## Structural changes
65
+
66
+ ### 4. The footer is a separate element, not part of the upload body
67
+
68
+ In the Figma the Cancel/Upload row sits in the Modal Sheet, outside the slot
69
+ holding the upload body. We matched that: `<rel-upload>` renders the alert,
70
+ dropzone and file list; `<rel-upload-actions>` renders the buttons and goes in
71
+ `<rel-modal>`'s footer slot. This is what keeps the footer **pinned while the
72
+ file list scrolls** — with five files the body scrolls and the buttons stay put.
73
+ The actions element finds its `<rel-upload>` sibling automatically.
74
+
75
+ ### 5. The dropzone is not itself a button
76
+
77
+ The design puts a "Browse files" button inside the dropzone. Making the whole
78
+ dropzone clickable *and* focusable would nest interactive controls inside each
79
+ other, which is an accessibility violation. The dropzone stays a drag-and-drop
80
+ target and a mouse click affordance, but "Browse files" is the only focusable
81
+ control — so keyboard and screen-reader users get exactly one clear action.
82
+
83
+ ### 6. Buttons and the alert are the standard DS components
84
+
85
+ Cancel/Upload are `rel-button`, and both alerts are `rel-alert` (warning and
86
+ error, subtle). They inherit the system's colours, states and focus rings rather
87
+ than the frame's local styles. One visible consequence: the design draws the
88
+ buttons as pills, which `rel-button` supports, but the disabled Upload uses the
89
+ system's disabled fill rather than the frame's dimmed grey. If design prefers the
90
+ dimmed look, that is a change to the *button* spec, not to upload.
91
+
92
+ ## Behaviour the design doesn't specify
93
+
94
+ ### 7. Validation failures render as error rows
95
+
96
+ The Figma Failure state shows rows reading "Upload failed - file exceeds the
97
+ 10 MB limit" and "Upload failed - unsupported file type". Both are knowable
98
+ *before* any transfer, so we surface rejected files as error rows immediately on
99
+ drop rather than silently discarding them. The wording is unchanged.
100
+
101
+ ### 8. Retry only re-sends what can succeed
102
+
103
+ A file rejected for format or size will fail identically every time, so
104
+ "Retry upload" skips it — and if *every* failure is of that kind the button is
105
+ disabled. Removing the offending row returns the card to the normal Upload state.
106
+
107
+ ### 9. "Upload" wins over "Retry" while anything is still pending
108
+
109
+ If some rows failed but others are still ready, the primary action stays
110
+ "Upload N files". Retry only appears once there is nothing left to send. The
111
+ Figma Multi-upload and Failure frames are both consistent with this.
112
+
113
+ ## States still missing from the Figma
114
+
115
+ These are implemented, but no frame specs them:
116
+
117
+ | State | What ships today |
118
+ | ----- | ---------------- |
119
+ | **Drag-over** | Dashed border becomes solid brand green, surface takes an 8% brand tint |
120
+ | **Disabled** | Whole card at 60% opacity, nothing interactive |
121
+ | **Dark mode** | Works automatically — every colour is a semantic token |
122
+
123
+ A frame for each (drag-over especially) would make the set complete.
124
+
125
+ ## Suggested Figma additions
126
+
127
+ 1. Drag-over variant
128
+ 2. Disabled variant
129
+ 3. Dark reference frame
130
+ 4. Apply the three colour corrections above to the shared styles
131
+
132
+ Questions and pushback welcome — none of this is precious, but items 1–3 and 5
133
+ fix measurable contrast or accessibility problems, so if the design goes a
134
+ different way it needs to solve those rather than reverting.
@@ -0,0 +1,163 @@
1
+ import { LitElement, html, css, nothing } from "lit";
2
+ import { customElement, property, state } from "lit/decorators.js";
3
+ import "@reliability-design/icons/rel-icon";
4
+ import "../rel-button/rel-button";
5
+ import type { RelUpload, UploadState } from "./rel-upload";
6
+
7
+ const EMPTY_STATE: UploadState = {
8
+ phase: "empty",
9
+ total: 0,
10
+ failed: 0,
11
+ canUpload: false,
12
+ primaryLabel: "Upload",
13
+ };
14
+
15
+ /**
16
+ * `<rel-upload-actions>` — the Cancel / Upload footer for `<rel-upload>`.
17
+ *
18
+ * Lives outside `<rel-upload>` so it can sit in `<rel-modal>`'s footer slot and
19
+ * stay pinned while the upload body scrolls. It mirrors the sibling
20
+ * `<rel-upload>`'s derived state, so the button label follows the design:
21
+ * "Upload" → "Upload 3 files" → spinner → "Retry upload".
22
+ *
23
+ * ```html
24
+ * <rel-modal open side="center" title="Upload documents">
25
+ * <rel-upload multiple .uploader=${fn}></rel-upload>
26
+ * <rel-upload-actions slot="footer"></rel-upload-actions>
27
+ * </rel-modal>
28
+ * ```
29
+ *
30
+ * The target is found automatically among its siblings. Use `for` to point at a
31
+ * specific `<rel-upload>` by id when a container holds more than one.
32
+ *
33
+ * @element rel-upload-actions
34
+ */
35
+ @customElement("rel-upload-actions")
36
+ export class RelUploadActions extends LitElement {
37
+ /** Id of the `<rel-upload>` to control. Omit to auto-discover a sibling. */
38
+ @property({ type: String }) for = "";
39
+
40
+ /** Cancel button label. */
41
+ @property({ type: String, attribute: "cancel-label" }) cancelLabel = "Cancel";
42
+
43
+ /** Hides Cancel for flows that only ever move forward. */
44
+ @property({ type: Boolean, attribute: "no-cancel" }) noCancel = false;
45
+
46
+ @state() private snapshot: UploadState = EMPTY_STATE;
47
+
48
+ private target: RelUpload | null = null;
49
+
50
+ static styles = css`
51
+ :host {
52
+ display: block;
53
+ font-family: var(--rel-fonts-font-family);
54
+ }
55
+
56
+ .actions {
57
+ display: flex;
58
+ align-items: center;
59
+ justify-content: flex-end;
60
+ gap: var(--rel-spacing-xs, 8px);
61
+ }
62
+
63
+ rel-button rel-icon {
64
+ margin-inline-end: var(--rel-spacing-xs, 8px);
65
+ }
66
+ `;
67
+
68
+ connectedCallback() {
69
+ super.connectedCallback();
70
+ // Siblings may not be upgraded yet when the footer slot connects first.
71
+ queueMicrotask(() => this.resolveTarget());
72
+ this.root.addEventListener("rel-state", this.onState as EventListener);
73
+ }
74
+
75
+ disconnectedCallback() {
76
+ super.disconnectedCallback();
77
+ this.root.removeEventListener("rel-state", this.onState as EventListener);
78
+ this.target = null;
79
+ }
80
+
81
+ updated(changed: Map<string, unknown>) {
82
+ if (changed.has("for")) this.resolveTarget();
83
+ }
84
+
85
+ /**
86
+ * Listen on our own root rather than `document`: a shared root means no
87
+ * shadow boundary sits between us and the sibling `<rel-upload>`, so
88
+ * `event.target` is never retargeted away from it.
89
+ */
90
+ private get root(): Document | ShadowRoot {
91
+ const node = this.getRootNode();
92
+ return node instanceof ShadowRoot ? node : this.ownerDocument;
93
+ }
94
+
95
+ private resolveTarget() {
96
+ const root = this.getRootNode() as ParentNode;
97
+ let found: RelUpload | null = null;
98
+
99
+ if (this.for) {
100
+ found = root.querySelector<RelUpload>(`#${CSS.escape(this.for)}`);
101
+ } else {
102
+ found =
103
+ this.parentElement?.querySelector<RelUpload>("rel-upload") ??
104
+ root.querySelector<RelUpload>("rel-upload");
105
+ }
106
+
107
+ this.target = found;
108
+ if (found) this.snapshot = found.state;
109
+ }
110
+
111
+ private onState = (e: CustomEvent<UploadState>) => {
112
+ if (!this.target) this.resolveTarget();
113
+ if (e.target !== this.target) return;
114
+ this.snapshot = e.detail;
115
+ };
116
+
117
+ private onCancel = () => {
118
+ this.target?.cancel();
119
+ this.dispatchEvent(new CustomEvent("rel-cancel", { bubbles: true, composed: true }));
120
+ };
121
+
122
+ private onPrimary = () => {
123
+ if (!this.target) return;
124
+ if (this.snapshot.phase === "failed") this.target.retry();
125
+ else this.target.upload();
126
+ };
127
+
128
+ render() {
129
+ const { phase, canUpload, primaryLabel } = this.snapshot;
130
+ const busy = phase === "uploading" || phase === "delayed";
131
+
132
+ return html`
133
+ <div class="actions" part="base">
134
+ ${this.noCancel
135
+ ? nothing
136
+ : html`<rel-button part="cancel" variant="secondary" @click=${this.onCancel}>
137
+ ${this.cancelLabel}
138
+ </rel-button>`}
139
+
140
+ <rel-button
141
+ part="primary"
142
+ variant="primary"
143
+ ?loading=${busy}
144
+ ?disabled=${!canUpload}
145
+ aria-label=${busy ? "Uploading" : primaryLabel}
146
+ @click=${this.onPrimary}
147
+ >
148
+ ${busy
149
+ ? nothing
150
+ : html`${phase === "failed"
151
+ ? html`<rel-icon name="refresh" size="sm"></rel-icon>`
152
+ : nothing}${primaryLabel}`}
153
+ </rel-button>
154
+ </div>
155
+ `;
156
+ }
157
+ }
158
+
159
+ declare global {
160
+ interface HTMLElementTagNameMap {
161
+ "rel-upload-actions": RelUploadActions;
162
+ }
163
+ }