@flytedan/flytebot-design-system 0.47.0 → 0.48.1

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.47.0",
3
+ "version": "0.48.1",
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",
@@ -791,6 +791,13 @@
791
791
  border-radius: var(--r-lg);
792
792
  box-shadow: var(--e-1);
793
793
  overflow: hidden;
794
+ /* MP-271: column flex so a card stretched taller than its own content (CardGrid's
795
+ equal-height rows — a row's shorter cards stretch to match the tallest) still keeps its
796
+ footer pinned to the bottom rather than leaving a gap above it. flex-direction: column on
797
+ a `<div>`/`<a>`/`<button>` all behave identically, so this holds regardless of which
798
+ element Card renders as (see `interactive` in Card.tsx). */
799
+ display: flex;
800
+ flex-direction: column;
794
801
  }
795
802
  .fd-card-flat {
796
803
  box-shadow: none;
@@ -809,6 +816,75 @@
809
816
  border-color: var(--border-strong);
810
817
  transform: translateY(-1px);
811
818
  }
819
+ /* MP-271: a real interactive card (Card rendered as <a> or <button> — `href`/`onClick`).
820
+ Reset the native element's own look (button chrome, link underline/colour) so it reads as
821
+ the same card either way, then add hover/press states that only ever touch box-shadow and
822
+ border-color — never `transform` or a size-affecting property — so nothing jumps or
823
+ reflows on press (MP-271 AC). A `:focus-visible` rule of its own IS needed, restated after
824
+ `:hover`/`:active` below: without it, the global `button:focus-visible`/`a:focus-visible`
825
+ rule (styles/foundations.css, specificity 0,1,1) loses to `.fd-card-interactive:hover`
826
+ (0,2,0) whenever a card is both keyboard-focused and still under the pointer — a real,
827
+ reachable state (tab to a card, and the mouse that was already resting on it never moved)
828
+ — which would hide the focus ring entirely. Same fix Button.tsx's own `:focus-visible`
829
+ rule already uses for the identical reason (see components.css's own comment on
830
+ `.fd-btn-icon-solid:focus-visible`). */
831
+ .fd-card-interactive {
832
+ display: block;
833
+ width: 100%;
834
+ text-align: left;
835
+ color: inherit;
836
+ text-decoration: none;
837
+ font: inherit;
838
+ cursor: pointer;
839
+ transition: box-shadow var(--dur-base) var(--ease), border-color var(--dur-base) var(--ease);
840
+ }
841
+ .fd-card-interactive:hover {
842
+ box-shadow: var(--e-2);
843
+ border-color: var(--border-strong);
844
+ }
845
+ .fd-card-interactive:active {
846
+ box-shadow: var(--e-1);
847
+ border-color: var(--border-strong);
848
+ }
849
+ .fd-card-interactive:focus-visible {
850
+ box-shadow: var(--focus-ring);
851
+ }
852
+ /* MP-271: the draft/placeholder variant — dashed instead of solid, using the same
853
+ `--border-strong` token an emphatic-but-still-neutral border already uses elsewhere
854
+ (Card's own `:hover` above), so light, dark and `[data-contrast="high"]` (tokens/
855
+ appearance.css: high contrast promotes `--border` to `--border-strong` itself) all
856
+ resolve a correct, themed dash with no new colour token. */
857
+ .fd-card-draft {
858
+ border-style: dashed;
859
+ border-color: var(--border-strong);
860
+ box-shadow: none;
861
+ }
862
+ /* MP-271 review (comment 2920, finding 3): an interactive Card (href/onClick) can no longer
863
+ render `action`/`footer` INSIDE the `<a>`/`<button>` — that was the only way this component
864
+ could ever produce a real `<button>` (or a link) nested inside a real `<a>`, which is invalid
865
+ HTML5 and double-fires clicks. `.fd-card-shell` is the plain, non-interactive wrapper Card
866
+ renders around the `<a>`/`<button>` only when it's interactive; `action` and `footer` become
867
+ its ordinary siblings, never its descendants, so nesting is impossible regardless of what a
868
+ caller puts in either slot. `footer` needs nothing extra (it was already the last thing
869
+ painted, so a sibling after the `<a>` lands in the exact same visual spot); `action` sits
870
+ inside the header row visually, so it's positioned back into that corner over the `<a>`,
871
+ given a higher stacking order so it stays independently clickable/focusable (the "stretched
872
+ link" technique's own z-index rule, applied to a real sibling instead of a pseudo-element). */
873
+ .fd-card-shell {
874
+ position: relative;
875
+ display: flex;
876
+ flex-direction: column;
877
+ height: 100%;
878
+ }
879
+ .fd-card-shell > .fd-card-interactive {
880
+ flex: 1 1 auto;
881
+ }
882
+ .fd-card-shell-action {
883
+ position: absolute;
884
+ top: 16px;
885
+ right: 18px;
886
+ z-index: 1;
887
+ }
812
888
  /* The header degrades in two steps as the action slot needs more room than is left:
813
889
  first the title gives up ITS OWN spare width (ordinary flex-shrink) down to
814
890
  fd-card-head-title's min-width; once even that doesn't leave enough room for the
@@ -839,6 +915,9 @@
839
915
  }
840
916
  .fd-card-body {
841
917
  padding: 18px;
918
+ /* MP-271: lets the body absorb any extra height CardGrid's equal-height stretch adds,
919
+ keeping `fd-card-foot` pinned to the bottom instead of leaving a gap above it. */
920
+ flex: 1 1 auto;
842
921
  }
843
922
  .fd-card-foot {
844
923
  display: flex;
@@ -3360,6 +3439,38 @@
3360
3439
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 175px), 1fr));
3361
3440
  gap: 16px;
3362
3441
  }
3442
+ /* MP-271 — CardGrid (src/components/layout/CardGrid.tsx). `auto-fill` rather than
3443
+ `fd-grid-stats`'s own `auto-fit` above on purpose: a dashboard of Cards should keep a
3444
+ consistent card width as the count changes, not stretch a lone card to fill the whole row
3445
+ the way a handful of stat tiles usefully do. `--card-grid-min` is set inline per instance
3446
+ (CardGrid's `minCardWidth` prop, default 280px); `1fr` is what lets the last, partial row's
3447
+ cards still share the leftover width evenly rather than sitting at a fixed size with a gap
3448
+ down one side. Gap is deliberately NOT set here — CardGrid applies one of the existing
3449
+ `fd-gap-*` utility classes above alongside this one, so a "medium gap" stays one token
3450
+ shared with every other flex/grid consumer of the spacing scale, not a second grid-only
3451
+ value. Equal-height rows need no explicit rule: `align-items: stretch` is Grid's own
3452
+ default, restated below only so it can't silently regress if that default is ever
3453
+ overridden elsewhere for `.fd-card-grid`'s selector specifically. */
3454
+ .fd-card-grid {
3455
+ display: grid;
3456
+ grid-template-columns: repeat(auto-fill, minmax(var(--card-grid-min, 280px), 1fr));
3457
+ align-items: stretch;
3458
+ }
3459
+ /* MP-271 review (comment 2920, finding 2): CardGrid wraps every item in its own
3460
+ `<div role="listitem">` rather than cloning `role` onto the child (a clone silently drops
3461
+ an added prop on a Fragment child, and can't fix that). The wrapper is now the real grid
3462
+ item that `align-items: stretch` above stretches to the row's height, so it has to pass
3463
+ that height on to its own one real child the same way `.fd-card-body`'s `flex: 1 1 auto`
3464
+ passes extra height down to `.fd-card-foot` — otherwise a stretched wrapper with an
3465
+ un-stretched child inside it would look identical to no equal-height rows at all. */
3466
+ .fd-card-grid > [role="listitem"] {
3467
+ display: flex;
3468
+ min-width: 0;
3469
+ }
3470
+ .fd-card-grid > [role="listitem"] > * {
3471
+ flex: 1 1 auto;
3472
+ min-width: 0;
3473
+ }
3363
3474
  @media (max-width: 640px) {
3364
3475
  .fd-grid-stats {
3365
3476
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 140px), 1fr));