@godxjp/ui 24.1.0 → 25.0.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.
Files changed (42) hide show
  1. package/dist/components/data-display/badge.js +3 -0
  2. package/dist/components/data-entry/select.js +7 -0
  3. package/dist/components/general/logo.d.ts +16 -0
  4. package/dist/components/general/logo.js +10 -2
  5. package/dist/components/layout/auth-identity.d.ts +1 -1
  6. package/dist/components/layout/auth-identity.js +5 -2
  7. package/dist/components/navigation/tabs.js +24 -6
  8. package/dist/contracts/measurement.json +1 -1
  9. package/dist/email/tokens.generated.d.ts +1 -1
  10. package/dist/email/tokens.generated.js +1 -1
  11. package/dist/props/components/layout.prop.d.ts +32 -1
  12. package/dist/props/registry.d.ts +4 -0
  13. package/dist/props/registry.js +5 -0
  14. package/dist/props/vocabulary/interaction.prop.d.ts +10 -1
  15. package/dist/styles/control.css +5 -1
  16. package/dist/styles/data-display-layout.css +16 -5
  17. package/dist/styles/data-entry-layout.css +5 -0
  18. package/dist/styles/layout.css +5 -0
  19. package/dist/styles/logo-layout.css +30 -3
  20. package/dist/styles/motion.css +4 -0
  21. package/dist/styles/shell-layout.css +11 -4
  22. package/dist/styles/text-layout.css +5 -0
  23. package/dist/tokens/components/logo.css +8 -0
  24. package/dist/tokens/components/shell.css +5 -3
  25. package/dist/tokens/derived.css +8 -8
  26. package/dist/tokens/foundation.css +2 -2
  27. package/docs/DESIGN-AUTHORITY.md +36 -22
  28. package/docs/FRAME-A11Y-CI.md +46 -17
  29. package/docs/foundation/colors.tsx +4 -1
  30. package/docs/general/logo.tsx +63 -0
  31. package/docs/general/typography.tsx +31 -0
  32. package/docs/layout/auth-identity.tsx +34 -0
  33. package/docs/showcase/acme-website.tsx +3 -1
  34. package/docs/showcase/case4-login.tsx +2 -2
  35. package/docs/showcase/case5-shift-calendar.tsx +5 -3
  36. package/docs/showcase/case6-agency-handy.tsx +13 -9
  37. package/docs/showcase/futurelastic-web.tsx +11 -3
  38. package/docs/showcase/table-bulk-actions.tsx +13 -2
  39. package/docs/showcase/table-density.tsx +3 -1
  40. package/docs/showcase/table-expandable-rows.tsx +25 -15
  41. package/docs/showcase/table-grouped-subtotals.tsx +5 -1
  42. package/package.json +2 -2
@@ -112,10 +112,13 @@ function Badge({
112
112
  const resolvedChildren = children ?? (status ? status in STATUS_MAP ? t(`status.${status}`) : status : void 0);
113
113
  const tinted = color != null && color !== "";
114
114
  const removeLabel = typeof children === "string" ? children : typeof resolvedChildren === "string" ? resolvedChildren : void 0;
115
+ const named = props["aria-label"] != null || props["aria-labelledby"] != null;
116
+ const nameRole = named && props.role === void 0 && !onRemove ? "img" : void 0;
115
117
  return /* @__PURE__ */ jsxs(
116
118
  Element,
117
119
  {
118
120
  "data-slot": "badge",
121
+ role: nameRole,
119
122
  "data-tone": tinted ? void 0 : resolvedTone,
120
123
  "data-tinted": tinted ? "" : void 0,
121
124
  "data-shape": shape ?? "default",
@@ -579,6 +579,10 @@ function DataSelect(props) {
579
579
  const showSearchConfig = typeof props.showSearch === "object" && props.showSearch !== null ? props.showSearch : void 0;
580
580
  const showSearch = showSearchConfig ? true : props.showSearch;
581
581
  const searchable = showSearch ?? (Boolean(props.loadOptions) || props.mode !== void 0);
582
+ const inheritedName = useFieldNameFallback({
583
+ "aria-label": props["aria-label"],
584
+ "aria-labelledby": props["aria-labelledby"]
585
+ });
582
586
  if (props.mode !== void 0 || searchable) {
583
587
  const antdFilter = showSearchConfig?.filterOption;
584
588
  const filterOption = antdFilter === void 0 ? props.filterOption : antdFilter === false ? () => true : antdFilter === true ? void 0 : (option, query) => antdFilter(query, option);
@@ -643,6 +647,8 @@ function DataSelect(props) {
643
647
  const ariaProps = Object.fromEntries(
644
648
  Object.entries(rest).filter(([key]) => key.startsWith("aria-"))
645
649
  );
650
+ const placeholderName = typeof placeholder === "string" ? placeholder : void 0;
651
+ const nameFromPlaceholder = ariaProps["aria-label"] === void 0 && ariaProps["aria-labelledby"] === void 0 && inheritedName["aria-label"] === void 0 && inheritedName["aria-labelledby"] === void 0 ? placeholderName : void 0;
646
652
  const optionTestId = (optionValue) => dataTestId ? `${dataTestId}-option-${optionValue}` : void 0;
647
653
  let flatIndex = 0;
648
654
  const renderItem = (option) => {
@@ -700,6 +706,7 @@ function DataSelect(props) {
700
706
  status,
701
707
  width,
702
708
  "aria-busy": loading || void 0,
709
+ "aria-label": nameFromPlaceholder,
703
710
  className: cn(
704
711
  prefix === void 0 || prefix === null ? void 0 : "ui-select-trigger-prefixed",
705
712
  (showClear || loading) && "ui-control-trigger-affixed",
@@ -39,6 +39,22 @@ export interface LogoProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, "
39
39
  * (a string, or a node when the name needs its own markup).
40
40
  */
41
41
  wordmark?: React.ReactNode;
42
+ /**
43
+ * The PRODUCT name that follows the brand, set off by the lockup's own rule — "GoDX | ID",
44
+ * "GoDX | Console", "GoDX | Admin" (gh#649). Pass the localized product name; it renders after
45
+ * `wordmark` when both are set.
46
+ *
47
+ * It is TEXT, in the lockup's own type scale, not artwork. The kit's flattened "GoDX | ID" file
48
+ * is a SECOND master (`0 0 1234 242` against this package's `30 30 871.29 182`) with its own
49
+ * hardcoded ink, its own global `id="title"`/`id="desc"` and a `#C5C8D6` rule; drawing the
50
+ * suffix would mean one more of those, one more dark variant and one more release per product
51
+ * name, and no localization at all. The brand's own construction — the G, the arrow, the "GoDX"
52
+ * logotype — stays untouched inside `mark="godx-lockup"`; a product name is not part of it.
53
+ *
54
+ * The rule's colour/width/height and the gap around it are `--logo-divider-*` /
55
+ * `--logo-product-suffix-*` tokens, so a service retunes them once rather than per page.
56
+ */
57
+ productSuffix?: React.ReactNode;
42
58
  /**
43
59
  * Accessible name for the mark. When set, the logo is exposed to assistive tech as an image with
44
60
  * this name; when omitted the mark is decorative (`aria-hidden`) — the correct default when a
@@ -10,6 +10,7 @@ import {
10
10
  } from "../../brand/godx-artwork.generated.js";
11
11
  import { Slot } from "../../lib/slot.js";
12
12
  import { cn } from "../../lib/utils.js";
13
+ import { VisuallyHidden } from "./visually-hidden.js";
13
14
  const X_HEIGHT_TOP = /^[acegmnopqrsuvwxyz]+$/;
14
15
  const LATIN_DESCENDER = /[gjpqy]/;
15
16
  function logoGlyphInk(glyph) {
@@ -86,6 +87,7 @@ function MarkArtwork({ mark, glyph }) {
86
87
  scheme
87
88
  )) });
88
89
  }
90
+ const GODX_LOGOTYPE_TEXT = "GoDX";
89
91
  const Logo = React.forwardRef(
90
92
  ({
91
93
  glyph = "g",
@@ -93,6 +95,7 @@ const Logo = React.forwardRef(
93
95
  size = "md",
94
96
  tone = "primary",
95
97
  wordmark,
98
+ productSuffix,
96
99
  label,
97
100
  asChild,
98
101
  children,
@@ -100,7 +103,10 @@ const Logo = React.forwardRef(
100
103
  ...props
101
104
  }, ref) => {
102
105
  const hasWordmark = wordmark !== void 0 && wordmark !== null && wordmark !== false;
106
+ const hasProductSuffix = productSuffix !== void 0 && productSuffix !== null && productSuffix !== false;
103
107
  const identity = { "data-mark": mark, "data-size": size, "data-tone": tone };
108
+ const drawnWordmark = mark === "godx-lockup" && !hasWordmark;
109
+ const suffixNamePrefix = drawnWordmark ? GODX_LOGOTYPE_TEXT : "";
104
110
  const renderRoot = (rootProps, content) => {
105
111
  if (!asChild) {
106
112
  return /* @__PURE__ */ jsx("span", { ref, ...rootProps, children: content });
@@ -108,7 +114,7 @@ const Logo = React.forwardRef(
108
114
  const borrowed = React.Children.only(children);
109
115
  return /* @__PURE__ */ jsx(Slot, { ...rootProps, ref, children: React.cloneElement(borrowed, void 0, content) });
110
116
  };
111
- if (hasWordmark) {
117
+ if (hasWordmark || hasProductSuffix) {
112
118
  return renderRoot(
113
119
  {
114
120
  "data-slot": "logo-lockup",
@@ -120,7 +126,9 @@ const Logo = React.forwardRef(
120
126
  },
121
127
  /* @__PURE__ */ jsxs(Fragment, { children: [
122
128
  /* @__PURE__ */ jsx("span", { "data-slot": "logo", ...identity, className: "ui-logo", "aria-hidden": "true", children: /* @__PURE__ */ jsx(MarkArtwork, { mark, glyph }) }),
123
- /* @__PURE__ */ jsx("span", { "data-slot": "logo-wordmark", "data-logotype": true, className: "ui-logo-wordmark", children: wordmark })
129
+ hasWordmark ? /* @__PURE__ */ jsx("span", { "data-slot": "logo-wordmark", "data-logotype": true, className: "ui-logo-wordmark", children: wordmark }) : null,
130
+ hasProductSuffix ? /* @__PURE__ */ jsx(VisuallyHidden, { style: { display: "block" }, children: suffixNamePrefix }) : null,
131
+ hasProductSuffix ? /* @__PURE__ */ jsx("span", { "data-slot": "logo-product-suffix", className: "ui-logo-product-suffix", children: productSuffix }) : null
124
132
  ] })
125
133
  );
126
134
  }
@@ -2,4 +2,4 @@ import type { AuthIdentityProp } from "../../props/components/layout.prop.js";
2
2
  export type { AuthIdentityProp } from "../../props/components/layout.prop.js";
3
3
  export type { AuthIdentityProp as AuthIdentityProps } from "../../props/components/layout.prop.js";
4
4
  /** Canonical hosted-identity mark, heading and optional real requesting-client context. */
5
- export declare function AuthIdentity({ title, requester, className }: AuthIdentityProp): import("react").JSX.Element;
5
+ export declare function AuthIdentity({ title, brand, requester, className }: AuthIdentityProp): import("react").JSX.Element;
@@ -1,10 +1,13 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { Clock } from "lucide-react";
3
+ import { isValidElement } from "react";
4
+ import { Slot } from "../../lib/slot.js";
3
5
  import { cn } from "../../lib/utils.js";
4
6
  import { Heading, Logo, Text } from "../general/index.js";
5
- function AuthIdentity({ title, requester, className }) {
7
+ function AuthIdentity({ title, brand, requester, className }) {
8
+ const artwork = brand === void 0 || brand === null ? /* @__PURE__ */ jsx(Logo, { mark: "godx", tone: "success" }) : isValidElement(brand) ? /* @__PURE__ */ jsx(Slot, { "aria-hidden": "true", children: brand }) : /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: brand });
6
9
  return /* @__PURE__ */ jsxs("div", { "data-slot": "auth-identity", className: cn("ui-auth-identity", className), children: [
7
- /* @__PURE__ */ jsx(Logo, { mark: "godx", tone: "success" }),
10
+ artwork,
8
11
  /* @__PURE__ */ jsx(Heading, { level: 1, children: title }),
9
12
  requester !== void 0 && requester !== null ? /* @__PURE__ */ jsxs("div", { "data-slot": "auth-requester", className: "ui-auth-requester", children: [
10
13
  /* @__PURE__ */ jsx("span", { "data-slot": "auth-requester-icon", className: "ui-auth-requester-icon", children: /* @__PURE__ */ jsx(Clock, { "aria-hidden": "true" }) }),
@@ -22,6 +22,8 @@ import {
22
22
  useTabsOverflowValues,
23
23
  useTabsScrollReporter
24
24
  } from "./tabs-scroll.js";
25
+ const TabsPanelRegistryContext = React.createContext(null);
26
+ const NO_PANELS = /* @__PURE__ */ new Set();
25
27
  function triggerSizeClassName(size) {
26
28
  return cn(
27
29
  size === "sm" && "min-h-[var(--tabs-trigger-height-sm)] px-[var(--tabs-trigger-padding-x-sm)] text-[length:var(--tabs-trigger-font-size-sm)]",
@@ -32,7 +34,8 @@ function triggerSizeClassName(size) {
32
34
  const TabsFrameContext = React.createContext({
33
35
  orientation: "horizontal",
34
36
  selectionSuppressed: false,
35
- size: "md"
37
+ size: "md",
38
+ panels: null
36
39
  });
37
40
  function resolveFallbackTabValue(items, requested) {
38
41
  if (!items || items.length === 0) return requested;
@@ -127,9 +130,19 @@ function Tabs({
127
130
  narrow
128
131
  );
129
132
  const selectionSuppressed = value === void 0 && items != null && items.length > 0 && resolvedDefault === void 0;
133
+ const [panels, setPanels] = React.useState(NO_PANELS);
134
+ const registerPanel = React.useCallback((panelValue) => {
135
+ setPanels((prev) => prev.has(panelValue) ? prev : new Set(prev).add(panelValue));
136
+ return () => setPanels((prev) => {
137
+ if (!prev.has(panelValue)) return prev;
138
+ const next = new Set(prev);
139
+ next.delete(panelValue);
140
+ return next;
141
+ });
142
+ }, []);
130
143
  const frame = React.useMemo(
131
- () => ({ orientation: resolvedOrientation, selectionSuppressed, size }),
132
- [resolvedOrientation, selectionSuppressed, size]
144
+ () => ({ orientation: resolvedOrientation, selectionSuppressed, size, panels }),
145
+ [resolvedOrientation, selectionSuppressed, size, panels]
133
146
  );
134
147
  const editable = variant === "editable-card";
135
148
  const collapsible = items != null && overflow === "menu";
@@ -312,7 +325,7 @@ function Tabs({
312
325
  })
313
326
  }
314
327
  ) : null;
315
- return /* @__PURE__ */ jsx(TabsFrameContext.Provider, { value: frame, children: /* @__PURE__ */ jsx(
328
+ return /* @__PURE__ */ jsx(TabsFrameContext.Provider, { value: frame, children: /* @__PURE__ */ jsx(TabsPanelRegistryContext.Provider, { value: registerPanel, children: /* @__PURE__ */ jsx(
316
329
  AriaTabs,
317
330
  {
318
331
  "data-slot": "tabs",
@@ -408,7 +421,7 @@ function Tabs({
408
421
  })
409
422
  ] }) : children
410
423
  }
411
- ) });
424
+ ) }) });
412
425
  }
413
426
  const TabsList = React.forwardRef(
414
427
  ({ className, variant = "default", loop: _loop, children, ...props }, ref) => {
@@ -453,7 +466,8 @@ const TabsList = React.forwardRef(
453
466
  TabsList.displayName = "TabsList";
454
467
  const TabsTrigger = React.forwardRef(
455
468
  ({ className, value, disabled, children, onKeyDown, onClick, ...props }, ref) => {
456
- const { orientation, selectionSuppressed, size } = React.useContext(TabsFrameContext);
469
+ const { orientation, selectionSuppressed, size, panels } = React.useContext(TabsFrameContext);
470
+ const controlsAPanel = panels === null || panels.has(value);
457
471
  return /* @__PURE__ */ jsx(
458
472
  AriaTab,
459
473
  {
@@ -490,6 +504,8 @@ const TabsTrigger = React.forwardRef(
490
504
  // here for BOTH states, exactly as Radix did.
491
505
  "data-state": renderProps.isSelected && !selectionSuppressed ? "active" : "inactive",
492
506
  "aria-selected": renderProps.isSelected && !selectionSuppressed,
507
+ // RAC writes this unconditionally; see `TabsFrame.panels` (gh#643).
508
+ "aria-controls": controlsAPanel ? domProps["aria-controls"] : void 0,
493
509
  "data-orientation": orientation,
494
510
  disabled,
495
511
  // CHAINED, not spread — the same reason `onKeyDown` below is. `withDomProps` lays the
@@ -523,6 +539,8 @@ TabsTrigger.displayName = "TabsTrigger";
523
539
  const TabsContent = React.forwardRef(
524
540
  ({ className, value, forceMount, children, ...props }, ref) => {
525
541
  const { orientation, selectionSuppressed } = React.useContext(TabsFrameContext);
542
+ const registerPanel = React.useContext(TabsPanelRegistryContext);
543
+ React.useEffect(() => registerPanel?.(value), [registerPanel, value]);
526
544
  if (selectionSuppressed && !forceMount) return null;
527
545
  return /* @__PURE__ */ jsx(
528
546
  AriaTabPanel,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
3
- "version": "24.1.0",
3
+ "version": "25.0.0",
4
4
  "targetSize": {
5
5
  "standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
6
6
  "min": 24,
@@ -61,7 +61,7 @@ export declare const EMAIL_COLOR_SOURCE: {
61
61
  };
62
62
  readonly urgencyForeground: {
63
63
  readonly cssVar: "--attention-foreground";
64
- readonly hsl: "60 33% 99%";
64
+ readonly hsl: "48 8% 13%";
65
65
  };
66
66
  };
67
67
  /** Dark-scheme colour sources, for `@media (prefers-color-scheme: dark)` overrides. */
@@ -53,7 +53,7 @@ const EMAIL_COLOR_SOURCE = {
53
53
  },
54
54
  "urgencyForeground": {
55
55
  "cssVar": "--attention-foreground",
56
- "hsl": "60 33% 99%"
56
+ "hsl": "48 8% 13%"
57
57
  }
58
58
  };
59
59
  const EMAIL_COLOR_SOURCE_DARK = {
@@ -630,8 +630,39 @@ export type AuthFooterProp = {
630
630
  * (`--auth-identity-gap` / `--auth-requester-*`) — a consumer never re-centres or re-spaces it.
631
631
  */
632
632
  export type AuthIdentityProp = {
633
- /** Primary auth heading, rendered as the page `h1`. */
633
+ /**
634
+ * Primary auth heading, rendered as the page `h1`. With `brand` set it is still the `h1` and
635
+ * still the block's accessible NAME; it simply stops being painted — see `brand`.
636
+ */
634
637
  title: ReactNode;
638
+ /**
639
+ * Brand artwork rendered in the mark's place, for a service whose design supplies a real
640
+ * lockup (mark + wordmark, sometimes a product suffix) rather than the bare GoDX mark. Leave
641
+ * it out and the canonical `<Logo mark="godx">` renders, which is right for every plain
642
+ * hosted-GoDX surface.
643
+ *
644
+ * The node takes the MARK's slot only: `data-slot="auth-identity"`, the `.ui-auth-identity`
645
+ * spacing and the `h1` contract are untouched, so a consumer swapping artwork does not fork
646
+ * the identity block to do it. Pass an inline `<svg>`, never a bitmap.
647
+ *
648
+ * IT IS RENDERED DECORATIVE, and that is measured rather than assumed (gh#652). The mark it
649
+ * replaces was always `aria-hidden` — the `h1` names this block — but a real LOCKUP is not a
650
+ * mark: `Logo mark="godx-lockup" productSuffix="ID"` puts "GoDX ID" into the accessibility tree
651
+ * as real text (gh#649's sr-only logotype plus the suffix), so left exposed beside an `h1` named
652
+ * "GoDX ID" the block announces the product TWICE — measured "GoDXIDGoDX ID" against the
653
+ * "GoDX ID" it has announced since it shipped. `aria-hidden` is merged ONTO the supplied element
654
+ * through `Slot` rather than onto a wrapper: a wrapper would be the flex item, and an
655
+ * `inline-flex` lockup inside a block box is back on a LINE BOX, whose strut descender lifts the
656
+ * mark a few px — the measured defect `Logo`'s own `asChild` prop exists to remove. A `brand`
657
+ * that is not a single element cannot take a merged prop and is wrapped instead; artwork always
658
+ * is one, which is why the doc above says to pass an inline `<svg>`.
659
+ *
660
+ * Keep `brand` NON-INTERACTIVE: `aria-hidden` over a focusable child is a WCAG failure.
661
+ *
662
+ * The painted `h1` stays. A lockup that already draws the product name therefore shows it twice
663
+ * on screen; that half of gh#652 is a separate decision and is not taken here.
664
+ */
665
+ brand?: ReactNode;
635
666
  /**
636
667
  * Optional real requesting-client context ("Attendance is requesting sign in"). Pass it ONLY
637
668
  * when the client identity is authoritative — never a placeholder.
@@ -938,6 +938,10 @@ export declare const COMPONENT_PROP_REGISTRY: {
938
938
  readonly group: "layout";
939
939
  readonly file: "components/layout.prop.ts";
940
940
  readonly vocabulary: readonly ["TitleProp", "ClassNameProp", {
941
+ readonly field: "brand";
942
+ readonly local: true;
943
+ readonly reason: "The product's own identity artwork (a Logo lockup), replacing the package mark AND the painted h1; a single element, because the block marks it decorative by merging aria-hidden onto it.";
944
+ }, {
941
945
  readonly field: "requester";
942
946
  readonly local: true;
943
947
  readonly reason: "Authoritative requesting-client context for a delegated auth flow (device grant / consent).";
@@ -978,6 +978,11 @@ const COMPONENT_PROP_REGISTRY = {
978
978
  vocabulary: [
979
979
  "TitleProp",
980
980
  "ClassNameProp",
981
+ {
982
+ field: "brand",
983
+ local: true,
984
+ reason: "The product's own identity artwork (a Logo lockup), replacing the package mark AND the painted h1; a single element, because the block marks it decorative by merging aria-hidden onto it."
985
+ },
981
986
  {
982
987
  field: "requester",
983
988
  local: true,
@@ -32,7 +32,16 @@ export type AvatarShapeProp = "circle" | "square";
32
32
  /** Text size — steps of the golden-ratio type scale (NEVER an arbitrary px). `sm` = base. */
33
33
  export type TextSizeProp = "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
34
34
  /** Text colour intent — maps to semantic foreground tokens (no raw palette). */
35
- export type TextToneProp = "default" | "muted" | "primary" | "success" | "warning" | "destructive" | "info";
35
+ /**
36
+ * `inherit` is the one that is NOT a colour: it takes whatever the surface already set (gh#643).
37
+ *
38
+ * Every other member paints an ABSOLUTE token, which is right on a page surface and wrong the
39
+ * moment the text sits on a coloured one — a brand fill, a tinted calendar block, a status band.
40
+ * There was no way to say "use this surface's own foreground", so `Text` on `bg-primary` painted
41
+ * `--foreground` over `--primary` and measured 2.45:1. Found by check:frame-axe on this package's
42
+ * OWN colour-token page, of all places, and on two showcases besides.
43
+ */
44
+ export type TextToneProp = "default" | "muted" | "primary" | "success" | "warning" | "destructive" | "info" | "inherit";
36
45
  /** Font weight — the reference-design canon is THREE weights only: `regular` (400 body), `medium` (500
37
46
  * heading/label), `bold` (700 emphasis). 600/`semibold` is forbidden. */
38
47
  export type FontWeightProp = "regular" | "medium" | "semibold" | "bold";
@@ -847,10 +847,14 @@
847
847
 
848
848
  .ui-search-input-clear {
849
849
  position: absolute;
850
+ display: grid;
851
+ min-inline-size: var(--touch-target-min);
852
+ min-block-size: var(--touch-target-min);
850
853
  inset-inline-end: var(--search-input-edge-inset);
851
854
  top: 50%;
852
- transform: translateY(-50%);
855
+ place-items: center;
853
856
  color: hsl(var(--muted-foreground));
857
+ transform: translateY(-50%);
854
858
  }
855
859
 
856
860
  .ui-search-input-clear:hover {
@@ -636,14 +636,25 @@
636
636
  flex-wrap: wrap;
637
637
  align-items: center;
638
638
  justify-content: center;
639
- gap: var(--space-2);
639
+
640
+ gap: 0;
640
641
  padding-top: var(--space-4);
641
642
  }
642
643
 
643
644
  .ui-carousel-dot {
645
+ display: grid;
646
+ min-inline-size: var(--touch-target-min);
647
+ min-block-size: var(--touch-target-min);
648
+ flex-shrink: 0;
649
+ place-items: center;
650
+ background: transparent;
651
+ }
652
+
653
+ .ui-carousel-dot::after {
654
+ content: "";
655
+ display: block;
644
656
  width: var(--space-2);
645
657
  height: var(--space-2);
646
- flex-shrink: 0;
647
658
  border-radius: var(--radius-pill);
648
659
  background: hsl(var(--border));
649
660
  transition:
@@ -651,11 +662,11 @@
651
662
  background-color 150ms ease;
652
663
  }
653
664
 
654
- .ui-carousel-dot:hover {
665
+ .ui-carousel-dot:hover::after {
655
666
  background: hsl(var(--muted-foreground));
656
667
  }
657
668
 
658
- .ui-carousel-dot[data-active] {
669
+ .ui-carousel-dot[data-active]::after {
659
670
  width: var(--space-5);
660
671
  background: hsl(var(--primary));
661
672
  }
@@ -664,7 +675,7 @@
664
675
  flex-direction: column;
665
676
  }
666
677
 
667
- .ui-carousel[data-orientation="vertical"] .ui-carousel-dot[data-active] {
678
+ .ui-carousel[data-orientation="vertical"] .ui-carousel-dot[data-active]::after {
668
679
  width: var(--space-2);
669
680
  height: var(--space-5);
670
681
  }
@@ -1,4 +1,9 @@
1
1
  @layer components {
2
+
3
+ .ui-attachments-input {
4
+ min-block-size: var(--touch-target-min);
5
+ }
6
+
2
7
  .ui-combobox-content {
3
8
  min-width: 14rem;
4
9
  width: min(22rem, 90vw);
@@ -79,6 +79,11 @@
79
79
  --separator-label-color: var(--separator-tone-info-label-color, var(--info));
80
80
  }
81
81
 
82
+ .ui-separator[data-tone="inherit"] {
83
+ --separator-rule-color: currentcolor;
84
+ --separator-label-color: currentcolor;
85
+ }
86
+
82
87
  .ui-aspect-ratio {
83
88
  overflow: clip;
84
89
  overflow-clip-margin: var(--focus-ring-clip-margin);
@@ -163,15 +163,18 @@
163
163
  white-space: nowrap;
164
164
  }
165
165
 
166
- .ui-logo-lockup[data-size="xs"] .ui-logo-wordmark {
166
+ .ui-logo-lockup[data-size="xs"] .ui-logo-wordmark,
167
+ .ui-logo-lockup[data-size="xs"] .ui-logo-product-suffix {
167
168
  font-size: var(--logo-wordmark-font-size-xs);
168
169
  }
169
170
 
170
- .ui-logo-lockup[data-size="sm"] .ui-logo-wordmark {
171
+ .ui-logo-lockup[data-size="sm"] .ui-logo-wordmark,
172
+ .ui-logo-lockup[data-size="sm"] .ui-logo-product-suffix {
171
173
  font-size: var(--logo-wordmark-font-size-sm);
172
174
  }
173
175
 
174
- .ui-logo-lockup[data-size="lg"] .ui-logo-wordmark {
176
+ .ui-logo-lockup[data-size="lg"] .ui-logo-wordmark,
177
+ .ui-logo-lockup[data-size="lg"] .ui-logo-product-suffix {
175
178
  font-size: var(--logo-wordmark-font-size-lg);
176
179
  }
177
180
 
@@ -179,6 +182,30 @@
179
182
  .ui-logo-lockup[data-tone="success"] .ui-logo-wordmark {
180
183
  color: hsl(var(--logo-wordmark-color, var(--logo-godx-color, var(--brand))));
181
184
  }
185
+
186
+ .ui-logo-lockup .ui-logo-product-suffix {
187
+ display: inline-flex;
188
+ align-items: center;
189
+ gap: var(--logo-product-suffix-gap);
190
+ font-family: var(--logo-wordmark-font-family, var(--font-family-display));
191
+ font-size: var(--logo-wordmark-font-size-md);
192
+ font-weight: var(--logo-product-suffix-font-weight);
193
+ letter-spacing: var(--logo-wordmark-letter-spacing);
194
+ line-height: 1;
195
+ color: hsl(var(--logo-product-suffix-color, var(--foreground)));
196
+ white-space: nowrap;
197
+ }
198
+
199
+ .ui-logo-lockup .ui-logo-product-suffix::before {
200
+ content: "";
201
+ flex: 0 0 auto;
202
+ inline-size: var(--logo-divider-width);
203
+ block-size: var(--logo-divider-height);
204
+ border-radius: var(--logo-divider-width);
205
+ background: hsl(
206
+ var(--logo-divider-color, var(--logo-godx-ink-color)) / var(--logo-divider-alpha)
207
+ );
208
+ }
182
209
  }
183
210
 
184
211
  [data-slot="logo-artwork"][data-scheme="dark"] {
@@ -88,6 +88,10 @@
88
88
  color: hsl(var(--text-info));
89
89
  }
90
90
 
91
+ .ui-activity[data-tone="inherit"] .ui-activity-mark {
92
+ color: inherit;
93
+ }
94
+
91
95
  .ui-activity-dot {
92
96
  inline-size: var(--activity-mark-size);
93
97
  block-size: var(--activity-mark-size);
@@ -1640,8 +1640,15 @@
1640
1640
  }
1641
1641
 
1642
1642
  .sb-nav-item[data-active="true"] {
1643
- background: var(--sidebar-item-active-background, hsl(var(--accent)));
1644
- color: var(--sidebar-item-active-foreground, hsl(var(--foreground)));
1643
+ background: var(
1644
+ --sidebar-item-active-background,
1645
+ color-mix(
1646
+ in srgb,
1647
+ hsl(var(--primary)) var(--sidebar-item-active-background-alpha),
1648
+ transparent
1649
+ )
1650
+ );
1651
+ color: var(--sidebar-item-active-foreground, hsl(var(--primary)));
1645
1652
  font-weight: var(--font-weight-medium);
1646
1653
  }
1647
1654
 
@@ -1758,8 +1765,8 @@
1758
1765
 
1759
1766
  .sb-nav-item--sub[data-active="true"] {
1760
1767
  background: transparent;
1761
- color: var(--sidebar-item-active-color, hsl(var(--primary)));
1762
- font-weight: 500;
1768
+ color: var(--sidebar-item-active-foreground, hsl(var(--primary)));
1769
+ font-weight: var(--font-weight-medium);
1763
1770
  }
1764
1771
 
1765
1772
  .sb-nav-item--sub[data-active="true"]::before {
@@ -58,6 +58,11 @@
58
58
  [data-slot="heading"][data-tone="muted"] {
59
59
  color: hsl(var(--muted-foreground));
60
60
  }
61
+
62
+ [data-slot="text"][data-tone="inherit"],
63
+ [data-slot="heading"][data-tone="inherit"] {
64
+ color: inherit;
65
+ }
61
66
  [data-slot="text"][data-tone="primary"],
62
67
  [data-slot="heading"][data-tone="primary"] {
63
68
  color: hsl(var(--primary));
@@ -46,6 +46,14 @@
46
46
  --logo-wordmark-letter-spacing: -0.01em;
47
47
  --logo-wordmark-font-family: initial;
48
48
  --logo-wordmark-color: initial;
49
+
50
+ --logo-divider-width: var(--stroke-hairline);
51
+ --logo-divider-height: 1em;
52
+ --logo-divider-alpha: 0.25;
53
+ --logo-divider-color: initial;
54
+ --logo-product-suffix-gap: var(--space-2);
55
+ --logo-product-suffix-font-weight: 500;
56
+ --logo-product-suffix-color: initial;
49
57
  }
50
58
 
51
59
  .dark,
@@ -211,12 +211,14 @@
211
211
  --app-launcher-launchpad-title-letter-spacing: 0.08em;
212
212
  --app-launcher-launchpad-close-space-padding: var(--space-2);
213
213
 
214
- --sidebar-item-active-color: initial;
215
- --sidebar-item-active-tint: initial;
216
-
217
214
  --sidebar-item-active-background: initial;
215
+
218
216
  --sidebar-item-active-foreground: initial;
219
217
 
218
+ --sidebar-item-active-tint: initial;
219
+
220
+ --sidebar-item-active-background-alpha: 12%;
221
+
220
222
  --auth-shell-control-height: var(--control-height-comfortable);
221
223
  --auth-shell-heading-size: var(--font-size-2xl);
222
224
  --auth-shell-card-max-width: 24rem;
@@ -2,15 +2,15 @@
2
2
 
3
3
  :root {
4
4
 
5
- --primary-hover: 206 100% 29.4%;
5
+ --primary-hover: 268.7 100% 41.6%;
6
6
 
7
- --primary-active: 208 100% 22%;
7
+ --primary-active: 268.7 100% 34.5%;
8
8
 
9
- --primary-border: 198 68% 66%;
9
+ --primary-border: 268.7 68% 66%;
10
10
  --destructive-hover: 359 50.6% 53.1%;
11
11
  --destructive-active: 355 70.6% 33.3%;
12
12
 
13
- --control-outline: 192.1 100% 44.7%;
13
+ --control-outline: 268.7 100% 44.7%;
14
14
  --control-outline-alpha: 0.11;
15
15
 
16
16
  --control-outline-error: 4.3 87.6% 34.7%;
@@ -22,15 +22,15 @@
22
22
  .dark,
23
23
  :root[data-theme="dark"] {
24
24
 
25
- --primary-hover: 202.2 74.6% 64.5%;
25
+ --primary-hover: 268.7 100% 92.7%;
26
26
 
27
- --primary-active: 200.2 81.3% 74.9%;
27
+ --primary-active: 268.7 100% 81.1%;
28
28
 
29
- --primary-border: 204.6 46.7% 23.5%;
29
+ --primary-border: 268.7 46.7% 23.5%;
30
30
  --destructive-hover: 358.8 42.3% 53.1%;
31
31
  --destructive-active: 357.3 50.6% 34.1%;
32
32
 
33
- --control-outline: 204.6 99% 61.8%;
33
+ --control-outline: 268.7 99% 61.8%;
34
34
  --control-outline-alpha: 0.29;
35
35
 
36
36
  --control-outline-error: 351.5 98.3% 53.5%;
@@ -26,13 +26,13 @@
26
26
  --input: 30 7% 53%;
27
27
 
28
28
  --success: 146 40% 58%;
29
- --success-foreground: 60 33% 99%;
29
+ --success-foreground: 48 8% 13%;
30
30
  --warning: 44 100% 49%;
31
31
  --warning-foreground: 48 8% 13%;
32
32
  --info: 221 40% 50%;
33
33
  --info-foreground: 60 33% 99%;
34
34
  --attention: 25 99% 46%;
35
- --attention-foreground: 60 33% 99%;
35
+ --attention-foreground: 48 8% 13%;
36
36
 
37
37
  --brand: 268.7 100% 50%;
38
38
  --brand-foreground: 60 33% 99%;