@flytedan/flytebot-design-system 0.12.11 → 0.12.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flytedan/flytebot-design-system",
3
- "version": "0.12.11",
3
+ "version": "0.12.13",
4
4
  "description": "flytedesk Design System — shared component library, tokens, and business-logic kits for flytedesk apps.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -244,7 +244,22 @@
244
244
  /* Soft: a lighter fill than solid, with a visibly darker border drawn around it (MP-52's
245
245
  "lighter fill, darker border" row action). Same --btn-soft-* tokens (tokens.css), same
246
246
  per-theme measurement discipline as solid, and the same pressed-selector-specificity
247
- fix baked in from the start (see the comment on .fd-btn-icon-solid:active above). */
247
+ fix baked in from the start (see the comment on .fd-btn-icon-solid:active above).
248
+
249
+ MP-114 hover/active: the base `.fd-btn-icon:hover`/`:active` rules above (plain
250
+ `.fd-btn-icon`, no variant) set `background` too, at the same (0,2,0) specificity as
251
+ `.fd-btn-icon-soft:hover`/`:active` — a tie the cascade breaks by source order, so
252
+ whichever rule sets `background` LAST wins. The old hover/active rules here only set
253
+ `filter` and `color`, never `background`, so they never entered that tie for the
254
+ `background` property at all: the base rule's `background: var(--surface-3)` /
255
+ `var(--border)` was the only declaration left standing, and the soft fill vanished
256
+ under a plain neutral surface colour while the ink stayed tuned for the soft fill —
257
+ e2e-measured at 1.11:1. `filter: brightness()` made it worse in a way no contrast
258
+ check (this file's or a screen reader user's own eye) could catch: a filter changes
259
+ the pixels without ever touching `background-color`, so it cannot be reasoned about
260
+ from the declared colours at all. Fixed on both counts: every state below restates a
261
+ real `background` from a real token, so it wins its own tie the same way solid's
262
+ already does, and there is no filter left to hide behind. */
248
263
  .fd-btn-icon-soft {
249
264
  background: var(--btn-soft-bg);
250
265
  color: var(--btn-soft-ink);
@@ -253,26 +268,36 @@
253
268
  }
254
269
  .fd-btn-icon-soft.is-ok {
255
270
  --btn-soft-bg: var(--btn-soft-ok-bg);
271
+ --btn-soft-bg-hover: var(--btn-soft-ok-bg-hover);
272
+ --btn-soft-bg-active: var(--btn-soft-ok-bg-active);
256
273
  --btn-soft-border: var(--btn-soft-ok-border);
257
274
  --btn-soft-ink: var(--btn-soft-ok-ink);
258
275
  }
259
276
  .fd-btn-icon-soft.is-danger {
260
277
  --btn-soft-bg: var(--btn-soft-danger-bg);
278
+ --btn-soft-bg-hover: var(--btn-soft-danger-bg-hover);
279
+ --btn-soft-bg-active: var(--btn-soft-danger-bg-active);
261
280
  --btn-soft-border: var(--btn-soft-danger-border);
262
281
  --btn-soft-ink: var(--btn-soft-danger-ink);
263
282
  }
264
283
  .fd-btn-icon-soft:hover,
265
284
  .fd-btn-icon-soft.is-ok:hover,
266
285
  .fd-btn-icon-soft.is-danger:hover {
267
- filter: brightness(1.08);
286
+ background: var(--btn-soft-bg-hover);
268
287
  color: var(--btn-soft-ink);
269
288
  }
289
+ /* Pressed restates every selector hover uses, same reasoning as
290
+ .fd-btn-icon-solid:active above. */
270
291
  .fd-btn-icon-soft:active,
271
292
  .fd-btn-icon-soft.is-ok:active,
272
293
  .fd-btn-icon-soft.is-danger:active {
273
- filter: brightness(0.92);
294
+ background: var(--btn-soft-bg-active);
295
+ color: var(--btn-soft-ink);
274
296
  box-shadow: none;
275
297
  }
298
+ /* Restated after :active, same reasoning as .fd-btn-icon-solid:focus-visible above: a
299
+ pressed-while-focused button must keep the ring, not the pressed rule's `box-shadow:
300
+ none`. */
276
301
  .fd-btn-icon-soft:focus-visible {
277
302
  box-shadow: var(--focus-ring);
278
303
  }
@@ -1699,6 +1724,14 @@
1699
1724
  }
1700
1725
 
1701
1726
  /* ============ Stat tile ============ */
1727
+ /* MP-141: kit-wide truncation rule for Stat (and every future text class here) —
1728
+ * explanatory/label copy (.fd-stat-sub, .fd-stat-label) always WRAPS, never
1729
+ * ellipsizes: a client reading a narrow tile must never lose part of a sentence
1730
+ * behind a "…" with no way to read the rest, and a tooltip is not a substitute
1731
+ * for readable client-facing copy. Only a user-data identifier displayed in a
1732
+ * dense row (a table cell, a chip) may ellipsize — and only when it also exposes
1733
+ * its full text via `title`/tooltip. Decide this once, here, for every Stat
1734
+ * consumer, not per screen. */
1702
1735
  .fd-stat {
1703
1736
  background: var(--surface);
1704
1737
  border: 1px solid var(--border);
@@ -1715,16 +1748,23 @@
1715
1748
  min-width: 0;
1716
1749
  }
1717
1750
  .fd-stat-sub {
1751
+ /* MP-141: this is explanatory copy ("on every funded campus, at minimum"),
1752
+ * never a user-data identifier — per the kit's truncation rule (see this
1753
+ * file's own header note above .fd-stat), explanatory/label copy always
1754
+ * wraps instead of ellipsizing, so a client reading a narrow tile never
1755
+ * loses part of the sentence behind a "…" with no way to read the rest. */
1718
1756
  display: block;
1719
1757
  margin-top: 4px;
1720
1758
  font-size: var(--body-sm-size);
1721
1759
  color: var(--text-2);
1722
1760
  min-width: 0;
1723
- overflow: hidden;
1724
- text-overflow: ellipsis;
1725
- white-space: nowrap;
1761
+ overflow-wrap: break-word;
1762
+ white-space: normal;
1726
1763
  }
1727
1764
  .fd-stat-label {
1765
+ /* MP-141: same rule as .fd-stat-sub above — this is the tile's fixed
1766
+ * caption, not a user-entered identifier, so it wraps rather than losing
1767
+ * words to an ellipsis. */
1728
1768
  display: block;
1729
1769
  margin-top: 6px;
1730
1770
  font-size: var(--overline-size);
@@ -1732,9 +1772,8 @@
1732
1772
  text-transform: uppercase;
1733
1773
  font-weight: var(--w-bold);
1734
1774
  color: var(--text-muted);
1735
- white-space: nowrap;
1736
- overflow: hidden;
1737
- text-overflow: ellipsis;
1775
+ overflow-wrap: break-word;
1776
+ white-space: normal;
1738
1777
  }
1739
1778
  .fd-stat-figure {
1740
1779
  flex: none;
@@ -6629,3 +6668,191 @@ button.fd-erow-metric:focus-visible {
6629
6668
  .fd-spinner{animation-duration:1.6s}
6630
6669
  .fd-arrived{animation:none}
6631
6670
  }
6671
+
6672
+ /* FileViewer (MP-130): a preview pane plus an optional host-supplied side panel — the
6673
+ companion notes/metadata surface the app composes in, generic here as `sidePanel`.
6674
+ Two regions, one scroll region each (R-8): `.fd-fv-body` never scrolls, each pane owns
6675
+ its own overflow. Side by side while there's room; below a narrow width of the
6676
+ component's OWN box (a container query, not a media query — the same reasoning as
6677
+ `.fd-tlist`'s above, since a FileViewer opened full-page and one opened in a Drawer see
6678
+ completely different widths at the same viewport) the side panel becomes a tab next to
6679
+ Preview and exactly one pane is visible at a time. */
6680
+ .fd-fv {
6681
+ container-type: inline-size;
6682
+ display: flex;
6683
+ flex-direction: column;
6684
+ height: 100%;
6685
+ min-height: 0;
6686
+ background: var(--surface);
6687
+ color: var(--text);
6688
+ }
6689
+ .fd-fv-head {
6690
+ display: flex;
6691
+ align-items: center;
6692
+ gap: 10px;
6693
+ padding: 12px 16px;
6694
+ border-bottom: 1px solid var(--border);
6695
+ flex: none;
6696
+ }
6697
+ .fd-fv-head-icon {
6698
+ display: grid;
6699
+ place-items: center;
6700
+ width: 32px;
6701
+ height: 32px;
6702
+ border-radius: var(--r-md);
6703
+ background: var(--surface-2);
6704
+ color: var(--text-2);
6705
+ font-size: 17px;
6706
+ flex: none;
6707
+ }
6708
+ .fd-fv-head-titles {
6709
+ display: flex;
6710
+ flex-direction: column;
6711
+ min-width: 0;
6712
+ flex: 1 1 auto;
6713
+ }
6714
+ .fd-fv-head-title {
6715
+ font-weight: 600;
6716
+ overflow: hidden;
6717
+ text-overflow: ellipsis;
6718
+ white-space: nowrap;
6719
+ }
6720
+ .fd-fv-head-meta {
6721
+ font-size: var(--body-sm-size);
6722
+ color: var(--text-2);
6723
+ }
6724
+ .fd-fv-head-actions {
6725
+ display: flex;
6726
+ align-items: center;
6727
+ gap: 8px;
6728
+ flex: none;
6729
+ }
6730
+ .fd-fv-download-error {
6731
+ margin: 10px 16px 0;
6732
+ padding: 8px 12px;
6733
+ border-radius: var(--r-md);
6734
+ background: var(--danger-fill);
6735
+ border: 1px solid var(--danger-border);
6736
+ color: var(--danger-text);
6737
+ font-size: var(--body-sm-size);
6738
+ }
6739
+ /* Only ever shown below the narrow container-query breakpoint (see the block at the
6740
+ bottom of this section) — at full width both panes are already visible, so a tab
6741
+ bar choosing between them would be a control with nothing to do. */
6742
+ .fd-fv-tabs {
6743
+ display: none;
6744
+ flex: none;
6745
+ padding: 0 16px;
6746
+ border-bottom: 1px solid var(--border);
6747
+ }
6748
+ .fd-fv-body {
6749
+ display: flex;
6750
+ flex: 1 1 auto;
6751
+ min-height: 0;
6752
+ overflow: hidden;
6753
+ }
6754
+ .fd-fv-preview {
6755
+ flex: 1 1 auto;
6756
+ min-width: 0;
6757
+ min-height: 0;
6758
+ overflow: auto;
6759
+ padding: 16px;
6760
+ }
6761
+ .fd-fv-side {
6762
+ flex: 0 0 320px;
6763
+ min-width: 0;
6764
+ min-height: 0;
6765
+ overflow: auto;
6766
+ padding: 16px;
6767
+ border-left: 1px solid var(--border);
6768
+ background: var(--surface-2);
6769
+ }
6770
+ .fd-fv-image {
6771
+ display: block;
6772
+ max-width: 100%;
6773
+ height: auto;
6774
+ margin: 0 auto;
6775
+ border-radius: var(--r-md);
6776
+ }
6777
+ .fd-fv-pdf {
6778
+ display: block;
6779
+ width: 100%;
6780
+ height: 100%;
6781
+ min-height: 480px;
6782
+ border: 0;
6783
+ border-radius: var(--r-md);
6784
+ background: var(--surface-2);
6785
+ }
6786
+ .fd-fv-fallback {
6787
+ display: flex;
6788
+ flex-direction: column;
6789
+ align-items: center;
6790
+ justify-content: center;
6791
+ gap: 8px;
6792
+ height: 100%;
6793
+ min-height: 220px;
6794
+ text-align: center;
6795
+ color: var(--text-2);
6796
+ }
6797
+ .fd-fv-fallback-icon {
6798
+ display: grid;
6799
+ place-items: center;
6800
+ width: 56px;
6801
+ height: 56px;
6802
+ border-radius: var(--r-full);
6803
+ background: var(--surface-2);
6804
+ font-size: 26px;
6805
+ }
6806
+ .fd-fv-fallback-title {
6807
+ font-weight: 600;
6808
+ color: var(--text);
6809
+ }
6810
+ .fd-fv-fallback-meta {
6811
+ font-size: var(--body-sm-size);
6812
+ }
6813
+ .fd-fv-csv-table {
6814
+ width: 100%;
6815
+ border-collapse: collapse;
6816
+ font-size: var(--body-sm-size);
6817
+ }
6818
+ .fd-fv-csv-table th,
6819
+ .fd-fv-csv-table td {
6820
+ padding: 6px 10px;
6821
+ border-bottom: 1px solid var(--border);
6822
+ text-align: left;
6823
+ white-space: nowrap;
6824
+ }
6825
+ .fd-fv-csv-table th {
6826
+ color: var(--text-2);
6827
+ font-weight: 600;
6828
+ background: var(--surface-2);
6829
+ position: sticky;
6830
+ top: 0;
6831
+ }
6832
+ .fd-fv-csv-note {
6833
+ margin-top: 10px;
6834
+ }
6835
+ /* Below 640px of `.fd-fv`'s OWN width (not the viewport's — see the block comment
6836
+ above) there is no room for a preview beside a side panel: the tab bar takes over
6837
+ choosing which one is visible, and the two panes stack full-width instead of sitting
6838
+ side by side. Each pane keeps its own `overflow: auto` from the base rules above, so
6839
+ R-8 (one scroll region per pane) holds at this width too — there is just one pane on
6840
+ screen at a time instead of two. */
6841
+ @container (max-width: 640px) {
6842
+ .fd-fv-tabs {
6843
+ display: flex;
6844
+ }
6845
+ .fd-fv-body {
6846
+ flex-direction: column;
6847
+ }
6848
+ .fd-fv-side {
6849
+ border-left: 0;
6850
+ border-top: 1px solid var(--border);
6851
+ }
6852
+ .fd-fv.is-tab-preview .fd-fv-side {
6853
+ display: none;
6854
+ }
6855
+ .fd-fv.is-tab-side .fd-fv-preview {
6856
+ display: none;
6857
+ }
6858
+ }
package/styles/tokens.css CHANGED
@@ -110,9 +110,18 @@
110
110
  usual badge sense (e.g. --ok-fill/--danger-fill) is only ~1.05:1 from --erow-danger-fill
111
111
  and fails outright; this is the lightest fill that still clears the bar next to a red
112
112
  row. The border reuses the darker --btn-solid-*-bg-active token, 1.8-2:1 off the new
113
- fill and 7:1+ off both grounds, so it reads even where hue alone would not. */
114
- --btn-soft-ok-bg:#557F6A;--btn-soft-ok-border:#145A38;--btn-soft-ok-ink:#FFFFFF;
115
- --btn-soft-danger-bg:#C84E43;--btn-soft-danger-border:#862720;--btn-soft-danger-ink:#FFFFFF;
113
+ fill and 7:1+ off both grounds, so it reads even where hue alone would not.
114
+
115
+ MP-114 hover/active: darkened the same way --btn-solid-* darkens for its own
116
+ hover/active (light theme gets darker as you press deeper). -bg-hover is 8% darker
117
+ than -bg, -bg-active 16% darker; white ink stays 5.19:1+ / 5.97:1+ and the fill stays
118
+ 4.45:1+ against --erow-danger-fill (the tighter of the two grounds) at every step.
119
+ These are real fills, not a `filter: brightness()` restatement — a filter changes
120
+ what the eye sees without changing `background-color`, so a contrast check (and a
121
+ person's actual reading of the button) that goes by the declared colour never sees
122
+ it move. */
123
+ --btn-soft-ok-bg:#557F6A;--btn-soft-ok-bg-hover:#4E7562;--btn-soft-ok-bg-active:#476B59;--btn-soft-ok-border:#145A38;--btn-soft-ok-ink:#FFFFFF;
124
+ --btn-soft-danger-bg:#C84E43;--btn-soft-danger-bg-hover:#B8483E;--btn-soft-danger-bg-active:#A84238;--btn-soft-danger-border:#862720;--btn-soft-danger-ink:#FFFFFF;
116
125
 
117
126
  --ok-fill:#DFF3E8;--ok-border:#A8DDC2;--ok-text:#0E5233;--ok-solid:#34A26B;--ok-emph:#57C68C;
118
127
  --warn-fill:#FDF1DA;--warn-border:#F0D49A;--warn-text:#6B4A05;--warn-solid:#C8890B;
@@ -279,9 +288,16 @@
279
288
  --surface-2 (#1B1F27) and --erow-danger-fill (#331813): white ink is 5.4:1 or better,
280
289
  the fill is 3.0:1 or better against either ground, and the border (the theme's own
281
290
  --btn-solid-*-bg, already proven legible here) is 1.9:1+ off the fill and 5.7:1+ off
282
- both grounds. */
283
- --btn-soft-ok-bg:#007A3D;--btn-soft-ok-border:#3FB37A;--btn-soft-ok-ink:#FFFFFF;
284
- --btn-soft-danger-bg:#D41200;--btn-soft-danger-border:#E8776D;--btn-soft-danger-ink:#FFFFFF;
291
+ both grounds.
292
+
293
+ MP-114 hover/active: rest is already only ~3.0:1 off --surface-2, the floor, so
294
+ darkening further (the light-theme direction) would drop the fill below 3:1 — both
295
+ states have to LIGHTEN here instead, same direction as --btn-solid-* on this ground.
296
+ -bg-hover is +6% toward white, -bg-active +9%/+12% (ok/danger): white ink stays
297
+ 4.61:1+ and the fill stays 3.30:1+ against both --surface-2 and --erow-danger-fill at
298
+ every step. Real fills, not `filter: brightness()` — see the :root comment above. */
299
+ --btn-soft-ok-bg:#007A3D;--btn-soft-ok-bg-hover:#0F8249;--btn-soft-ok-bg-active:#17864E;--btn-soft-ok-border:#3FB37A;--btn-soft-ok-ink:#FFFFFF;
300
+ --btn-soft-danger-bg:#D41200;--btn-soft-danger-bg-hover:#D7200F;--btn-soft-danger-bg-active:#D92E1F;--btn-soft-danger-border:#E8776D;--btn-soft-danger-ink:#FFFFFF;
285
301
 
286
302
  /* chat kit — see the :root block above */
287
303
  --chat-code-bg:#12151B;