@godxjp/ui 27.3.1 → 27.5.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 (53) hide show
  1. package/dist/components/data-entry/upload-file-kind.d.ts +25 -0
  2. package/dist/components/data-entry/upload-file-kind.js +88 -0
  3. package/dist/components/data-entry/upload.d.ts +2 -2
  4. package/dist/components/data-entry/upload.js +10 -6
  5. package/dist/components/general/icon.d.ts +31 -0
  6. package/dist/components/general/icon.js +23 -0
  7. package/dist/components/general/index.d.ts +2 -0
  8. package/dist/components/general/index.js +2 -0
  9. package/dist/components/layout/flex.d.ts +2 -2
  10. package/dist/components/layout/flex.js +7 -1
  11. package/dist/components/layout/topbar-item.d.ts +1 -0
  12. package/dist/components/layout/topbar-item.js +3 -1
  13. package/dist/components/ui/avatar.d.ts +1 -1
  14. package/dist/components/ui/avatar.js +2 -0
  15. package/dist/components/ui/segmented.d.ts +7 -2
  16. package/dist/components/ui/toggle.d.ts +2 -2
  17. package/dist/components/ui/toggle.js +4 -0
  18. package/dist/contracts/measurement.json +1 -1
  19. package/dist/props/components/data-display.prop.d.ts +14 -0
  20. package/dist/props/components/data-entry.prop.d.ts +25 -0
  21. package/dist/props/components/general.prop.d.ts +42 -1
  22. package/dist/props/components/layout.prop.d.ts +42 -0
  23. package/dist/props/registry.d.ts +37 -1
  24. package/dist/props/registry.js +43 -1
  25. package/dist/props/vocabulary/index.d.ts +1 -1
  26. package/dist/props/vocabulary/interaction.prop.d.ts +11 -0
  27. package/dist/styles/control.css +12 -0
  28. package/dist/styles/core.css +2 -0
  29. package/dist/styles/data-display-layout.css +35 -5
  30. package/dist/styles/data-entry-layout.css +22 -0
  31. package/dist/styles/icon-layout.css +37 -0
  32. package/dist/styles/index.css +2 -0
  33. package/dist/styles/shell-layout.css +10 -0
  34. package/dist/styles/text-layout.css +16 -8
  35. package/dist/tokens/components/control.css +1 -0
  36. package/dist/tokens/components/data-display.css +26 -0
  37. package/dist/tokens/components/segmented.css +4 -0
  38. package/dist/tokens/components/upload.css +4 -0
  39. package/docs/CONSUMER-RULES.md +2 -2
  40. package/docs/CUSTOMER-THEMING.md +26 -0
  41. package/docs/FRAME-COVERAGE-REPORT.md +3 -2
  42. package/docs/data-display/avatar.tsx +128 -11
  43. package/docs/data-display/prose.tsx +64 -0
  44. package/docs/data-entry/segmented.tsx +55 -5
  45. package/docs/data-entry/toggle-group.tsx +15 -3
  46. package/docs/data-entry/toggle.tsx +39 -1
  47. package/docs/data-entry/upload.tsx +38 -0
  48. package/docs/general/icon.tsx +215 -0
  49. package/docs/layout/flex.tsx +59 -0
  50. package/docs/layout/topbar-item.tsx +29 -1
  51. package/package.json +2 -2
  52. package/scripts/consumer-rule.md +18 -17
  53. package/scripts/ui-audit.mjs +128 -0
@@ -434,6 +434,11 @@ export declare const VOCABULARY_REGISTRY: {
434
434
  readonly category: "interaction";
435
435
  readonly description: "Text size — golden-ratio type-scale steps (2xs…2xl), never an arbitrary px";
436
436
  };
437
+ readonly IconSizeProp: {
438
+ readonly file: "vocabulary/interaction.prop.ts";
439
+ readonly category: "interaction";
440
+ readonly description: "Icon size — the nine --icon-size-* scale steps (2xs…4xl), never an arbitrary px or a size- utility";
441
+ };
437
442
  readonly TextToneProp: {
438
443
  readonly file: "vocabulary/interaction.prop.ts";
439
444
  readonly category: "interaction";
@@ -746,6 +751,11 @@ export declare const COMPONENT_PROP_REGISTRY: {
746
751
  readonly file: "components/layout.prop.ts";
747
752
  readonly vocabulary: readonly [];
748
753
  };
754
+ readonly FlexMarkerProp: {
755
+ readonly group: "layout";
756
+ readonly file: "components/layout.prop.ts";
757
+ readonly vocabulary: readonly [];
758
+ };
749
759
  readonly FlexAlignProp: {
750
760
  readonly group: "layout";
751
761
  readonly file: "components/layout.prop.ts";
@@ -767,6 +777,10 @@ export declare const COMPONENT_PROP_REGISTRY: {
767
777
  readonly field: "direction";
768
778
  readonly local: true;
769
779
  readonly reason: "Flex-specific axis control.";
780
+ }, {
781
+ readonly field: "marker";
782
+ readonly local: true;
783
+ readonly reason: 'List-marker seam for as="ul"/"ol" (disc | decimal | none). marker="none" emits no data-list, so a list keeps its element, its <li> semantics and its gap token while losing the bullet and the --space-5 indent — the home <ListRow as="li"> had none of, which forced a raw <ul> (gh#714).';
770
784
  }, "GapProp", {
771
785
  readonly field: "align";
772
786
  readonly local: true;
@@ -1203,6 +1217,23 @@ export declare const COMPONENT_PROP_REGISTRY: {
1203
1217
  readonly reason: "Ambient live-region opt-in — ActivityAnnounceProp vocabulary; deliberately NOT a boolean so the value names the politeness level.";
1204
1218
  }];
1205
1219
  };
1220
+ readonly IconGlyphProp: {
1221
+ readonly group: "general";
1222
+ readonly file: "components/general.prop.ts";
1223
+ readonly vocabulary: readonly ["IconProp", "IconSizeProp", "TextToneProp", "LabelProp", "ClassNameProp", {
1224
+ readonly field: "label";
1225
+ readonly local: true;
1226
+ readonly reason: "LabelProp narrowed to string: this becomes the glyph's aria-label, and an accessible name is TEXT — a ReactNode cannot be one.";
1227
+ }, {
1228
+ readonly field: "as";
1229
+ readonly local: true;
1230
+ readonly reason: "The glyph COMPONENT to render — the IconProp concept, widened to a component that also forwards SVG props and a ref, because Icon renders ONTO the glyph (the sized element is the <svg> itself) rather than wrapping it.";
1231
+ }, {
1232
+ readonly field: "size";
1233
+ readonly local: true;
1234
+ readonly reason: "IconSizeProp, the nine-step icon scale — deliberately NOT the four-step control SizeProp: a glyph beside 2xs caption text and a 48px empty-state mark are the same primitive.";
1235
+ }];
1236
+ };
1206
1237
  readonly FloatButtonTypeProp: {
1207
1238
  readonly group: "general";
1208
1239
  readonly file: "components/general.prop.ts";
@@ -1619,6 +1650,11 @@ export declare const COMPONENT_PROP_REGISTRY: {
1619
1650
  readonly file: "components/data-entry.prop.ts";
1620
1651
  readonly vocabulary: readonly [];
1621
1652
  };
1653
+ readonly UploadListTypeProp: {
1654
+ readonly group: "data-entry";
1655
+ readonly file: "components/data-entry.prop.ts";
1656
+ readonly vocabulary: readonly [];
1657
+ };
1622
1658
  readonly TreeOptionProp: {
1623
1659
  readonly group: "data-entry";
1624
1660
  readonly file: "components/data-entry.prop.ts";
@@ -1677,7 +1713,7 @@ export declare const COMPONENT_PROP_REGISTRY: {
1677
1713
  readonly AvatarProp: {
1678
1714
  readonly group: "data-display";
1679
1715
  readonly file: "components/data-display.prop.ts";
1680
- readonly vocabulary: readonly ["AvatarShapeProp", "LabelProp", "ChildrenProp", "ClassNameProp"];
1716
+ readonly vocabulary: readonly ["AvatarShapeProp", "SizeProp", "LabelProp", "ChildrenProp", "ClassNameProp"];
1681
1717
  };
1682
1718
  readonly AvatarAppearanceProp: {
1683
1719
  readonly group: "data-display";
@@ -433,6 +433,11 @@ const VOCABULARY_REGISTRY = {
433
433
  category: "interaction",
434
434
  description: "Text size \u2014 golden-ratio type-scale steps (2xs\u20262xl), never an arbitrary px"
435
435
  },
436
+ IconSizeProp: {
437
+ file: "vocabulary/interaction.prop.ts",
438
+ category: "interaction",
439
+ description: "Icon size \u2014 the nine --icon-size-* scale steps (2xs\u20264xl), never an arbitrary px or a size- utility"
440
+ },
436
441
  TextToneProp: {
437
442
  file: "vocabulary/interaction.prop.ts",
438
443
  category: "interaction",
@@ -767,6 +772,7 @@ const COMPONENT_PROP_REGISTRY = {
767
772
  ]
768
773
  },
769
774
  FlexDirectionProp: { group: "layout", file: "components/layout.prop.ts", vocabulary: [] },
775
+ FlexMarkerProp: { group: "layout", file: "components/layout.prop.ts", vocabulary: [] },
770
776
  FlexAlignProp: { group: "layout", file: "components/layout.prop.ts", vocabulary: [] },
771
777
  FlexJustifyProp: { group: "layout", file: "components/layout.prop.ts", vocabulary: [] },
772
778
  FlexProp: {
@@ -779,6 +785,11 @@ const COMPONENT_PROP_REGISTRY = {
779
785
  reason: "Closed tag seam (div | span) so a Flex stays valid HTML inside a phrasing-only parent such as the <button> a TabsTrigger renders \u2014 same shape as ListRow's `as` (gh#354)."
780
786
  },
781
787
  { field: "direction", local: true, reason: "Flex-specific axis control." },
788
+ {
789
+ field: "marker",
790
+ local: true,
791
+ reason: 'List-marker seam for as="ul"/"ol" (disc | decimal | none). marker="none" emits no data-list, so a list keeps its element, its <li> semantics and its gap token while losing the bullet and the --space-5 indent \u2014 the home <ListRow as="li"> had none of, which forced a raw <ul> (gh#714).'
792
+ },
782
793
  "GapProp",
783
794
  { field: "align", local: true, reason: "Flex-specific align-items keyword subset." },
784
795
  { field: "justify", local: true, reason: "Flex-specific justify-content keyword subset." },
@@ -1328,6 +1339,32 @@ const COMPONENT_PROP_REGISTRY = {
1328
1339
  }
1329
1340
  ]
1330
1341
  },
1342
+ IconGlyphProp: {
1343
+ group: "general",
1344
+ file: "components/general.prop.ts",
1345
+ vocabulary: [
1346
+ "IconProp",
1347
+ "IconSizeProp",
1348
+ "TextToneProp",
1349
+ "LabelProp",
1350
+ "ClassNameProp",
1351
+ {
1352
+ field: "label",
1353
+ local: true,
1354
+ reason: "LabelProp narrowed to string: this becomes the glyph's aria-label, and an accessible name is TEXT \u2014 a ReactNode cannot be one."
1355
+ },
1356
+ {
1357
+ field: "as",
1358
+ local: true,
1359
+ reason: "The glyph COMPONENT to render \u2014 the IconProp concept, widened to a component that also forwards SVG props and a ref, because Icon renders ONTO the glyph (the sized element is the <svg> itself) rather than wrapping it."
1360
+ },
1361
+ {
1362
+ field: "size",
1363
+ local: true,
1364
+ reason: "IconSizeProp, the nine-step icon scale \u2014 deliberately NOT the four-step control SizeProp: a glyph beside 2xs caption text and a 48px empty-state mark are the same primitive."
1365
+ }
1366
+ ]
1367
+ },
1331
1368
  FloatButtonTypeProp: {
1332
1369
  group: "general",
1333
1370
  file: "components/general.prop.ts",
@@ -1898,6 +1935,11 @@ const COMPONENT_PROP_REGISTRY = {
1898
1935
  vocabulary: []
1899
1936
  },
1900
1937
  UploadVariantProp: { group: "data-entry", file: "components/data-entry.prop.ts", vocabulary: [] },
1938
+ UploadListTypeProp: {
1939
+ group: "data-entry",
1940
+ file: "components/data-entry.prop.ts",
1941
+ vocabulary: []
1942
+ },
1901
1943
  TreeOptionProp: {
1902
1944
  group: "data-entry",
1903
1945
  file: "components/data-entry.prop.ts",
@@ -2005,7 +2047,7 @@ const COMPONENT_PROP_REGISTRY = {
2005
2047
  AvatarProp: {
2006
2048
  group: "data-display",
2007
2049
  file: "components/data-display.prop.ts",
2008
- vocabulary: ["AvatarShapeProp", "LabelProp", "ChildrenProp", "ClassNameProp"]
2050
+ vocabulary: ["AvatarShapeProp", "SizeProp", "LabelProp", "ChildrenProp", "ClassNameProp"]
2009
2051
  },
2010
2052
  AvatarAppearanceProp: {
2011
2053
  group: "data-display",
@@ -2,6 +2,6 @@
2
2
  export type { ClassNameProp, ChildrenProp, IdProp, OpenProp, DefaultOpenProp, OnOpenChangeProp, HandlerProp, PendingProp, RequiredProp, DisabledProp, LabelProp, HelperProp, ErrorProp, ErrorBagProp, PlaceholderProp, NameProp, ValueProp, DefaultValueProp, OnValueChangeProp, OnChangeProp, OnClickProp, AsChildProp, FlushProp, WidthProp, ControlWidthProp, AllowClearProp, MaxTagCountProp, MaxTagPlaceholderProp, NotFoundContentProp, PopupMatchWidthProp, } from "./shared.prop.js";
3
3
  export type { TitleProp, SubtitleProp, StatusProp, DescriptionProp, ExtraProp, FooterProp, ActionProp, IconProp, ConfirmLabelProp, CancelLabelProp, ActionsProp, EmptyMessageProp, } from "./content.prop.js";
4
4
  export type { PageDensityProp, PageContainerVariantProp, CenteredShellWidthProp, CenteredShellAlignProp, CenteredShellPresetProp, ErrorSurfaceModeProp, ErrorSurfaceStatusProp, AuthShellPresetProp, MobileShellHeightProp, MobileShellWidthProp, OrientationProp, TableDensityProp, DensityProp, GapNameProp, PadProp, PadRawProp, PadSides, GapProp, GapStepProp, } from "./layout.prop.js";
5
- export type { ButtonVariantProp, ButtonSizeProp, BadgeVariantProp, AppSettingPickerAppearanceProp, AppSettingToggleAppearanceProp, ShapeProp, AvatarShapeProp, TextSizeProp, TextToneProp, FontWeightProp, HeadingLevelProp, TextAlignProp, TextWhitespaceProp, TypographyTypeProp, TitleLevelProp, TypographyCopyConfigProp, TypographyEditConfigProp, TypographyEllipsisConfigProp, TypographyActionsConfigProp, SizeProp, ControlStatusProp, ControlVariantProp, FormLayoutProp, DescriptionsLayoutProp, BreakpointProp, ConfirmVariantProp, ToneProp, AlertVariantProp, SortDirectionProp, ColumnAlignProp, SortStateProp, RevealDelayProp, ActivityVariantProp, ActivityAnnounceProp, } from "./interaction.prop.js";
5
+ export type { ButtonVariantProp, ButtonSizeProp, BadgeVariantProp, AppSettingPickerAppearanceProp, AppSettingToggleAppearanceProp, ShapeProp, AvatarShapeProp, TextSizeProp, TextToneProp, FontWeightProp, HeadingLevelProp, TextAlignProp, TextWhitespaceProp, TypographyTypeProp, TitleLevelProp, TypographyCopyConfigProp, TypographyEditConfigProp, TypographyEllipsisConfigProp, TypographyActionsConfigProp, SizeProp, ControlStatusProp, ControlVariantProp, FormLayoutProp, DescriptionsLayoutProp, BreakpointProp, ConfirmVariantProp, ToneProp, AlertVariantProp, SortDirectionProp, ColumnAlignProp, SortStateProp, RevealDelayProp, ActivityVariantProp, ActivityAnnounceProp, IconSizeProp, } from "./interaction.prop.js";
6
6
  export type { BreadcrumbItemProp, BreadcrumbProp, BreadcrumbItemMenuProp, BreadcrumbItemMenuEntryProp, BreadcrumbSeparatorProp, BreadcrumbItemRenderProp, } from "./navigation.prop.js";
7
7
  export type { GetRowIdProp, GetRowLabelProp, OnRowClickProp, ColumnDefProp, TablePresetProp, TableColumnPriorityProp, TableCellIndentProp, SelectedIdsProp, OnSelectChangeProp, OnTableDensityChangeProp, TableRowToneProp, RowToneProp, OnSortChangeProp, OnSearchChangeProp, OnClearFiltersProp, HasActiveFiltersProp, StickyProp, ColumnFixedProp, ColumnFilterValueProp, ColumnFilterItemProp, ColumnFilterStateProp, OnColumnFilterChangeProp, ColumnCompareProp, ColumnSorterProp, TableSelectionItemProp, TableRowSelectionProp, TableExpandableProp, TableSummaryProp, TableScrollProp, TableStickyProp, OnRowProp, TablePaginationProp, TablePaginationPositionProp, DescriptionsColumnProp, DescriptionsSpanProp, DescriptionsItemsProp, } from "./data.prop.js";
@@ -31,6 +31,17 @@ export type ShapeProp = "default" | "pill" | "sharp";
31
31
  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
+ /**
35
+ * Icon size — the NINE steps of the `--icon-size-*` scale (docs/TOKENS.md · gh#326), not the four
36
+ * of the control `SizeProp`.
37
+ *
38
+ * The icon axis already had a vocabulary before it had a name, and it is a FIXED LIST rather than
39
+ * a ratio: 14/16 = 0.875 but 20/16 = 1.25, because a 1px-stroke glyph cannot sit on half a pixel.
40
+ * Naming the whole list here — as `TextSizeProp` does for type — is what lets a consumer put a
41
+ * standalone glyph on the scale (`<Icon as={Lock} size="sm" />`) instead of reaching for a
42
+ * `className="size-4"` the consumer rules forbid.
43
+ */
44
+ export type IconSizeProp = "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl";
34
45
  /** Text colour intent — maps to semantic foreground tokens (no raw palette). */
35
46
  /**
36
47
  * `inherit` is the one that is NOT a colour: it takes whatever the surface already set (gh#643).
@@ -31,6 +31,12 @@
31
31
  padding-inline: var(--space-3);
32
32
  }
33
33
 
34
+ .ui-toggle-xs {
35
+ min-height: var(--control-height-xs);
36
+ padding-inline: var(--space-1);
37
+ font-size: var(--toggle-xs-font-size);
38
+ }
39
+
34
40
  .ui-toggle-sm {
35
41
  min-height: var(--control-height-sm);
36
42
  padding-inline: var(--space-2);
@@ -3135,6 +3141,12 @@
3135
3141
  justify-content: flex-start;
3136
3142
  }
3137
3143
 
3144
+ .ui-segmented[data-size="xs"] {
3145
+ --control-height: var(--control-height-xs);
3146
+ --segmented-item-padding-inline: var(--segmented-xs-item-padding-inline);
3147
+ font-size: var(--segmented-xs-font-size);
3148
+ }
3149
+
3138
3150
  .ui-segmented[data-size="sm"] {
3139
3151
  --control-height: var(--control-height-sm);
3140
3152
  }
@@ -23,3 +23,5 @@
23
23
  @import "./navigation-layout.css";
24
24
  @import "./chart-layout.css";
25
25
  @import "./float-button-layout.css";
26
+
27
+ @import "./icon-layout.css";
@@ -2,8 +2,8 @@
2
2
  .ui-avatar {
3
3
  position: relative;
4
4
  display: inline-flex;
5
- width: var(--control-height);
6
- height: var(--control-height);
5
+ width: var(--avatar-size);
6
+ height: var(--avatar-size);
7
7
  flex-shrink: 0;
8
8
  align-items: center;
9
9
  justify-content: center;
@@ -13,7 +13,7 @@
13
13
  linear-gradient(var(--avatar-tint), var(--avatar-tint)),
14
14
  var(--avatar-background, hsl(var(--muted)));
15
15
  color: hsl(var(--muted-foreground));
16
- font-size: var(--font-size-base);
16
+ font-size: var(--avatar-font-size);
17
17
  font-weight: var(--font-weight-medium);
18
18
  }
19
19
 
@@ -36,6 +36,36 @@
36
36
  flex: none;
37
37
  }
38
38
 
39
+ .ui-avatar[data-size="xs"] {
40
+ --avatar-size: var(--avatar-size-xs);
41
+ --avatar-square-size: var(--avatar-size-xs);
42
+ --avatar-font-size: var(--avatar-font-size-xs);
43
+ --avatar-glyph-size: var(--avatar-glyph-size-xs);
44
+ --avatar-tinted-glyph-size: var(--avatar-glyph-size-xs);
45
+ }
46
+
47
+ .ui-avatar[data-size="sm"] {
48
+ --avatar-size: var(--avatar-size-sm);
49
+ --avatar-square-size: var(--avatar-size-sm);
50
+ --avatar-font-size: var(--avatar-font-size-sm);
51
+ --avatar-glyph-size: var(--avatar-glyph-size-sm);
52
+ --avatar-tinted-glyph-size: var(--avatar-glyph-size-sm);
53
+ }
54
+
55
+ .ui-avatar[data-size="lg"] {
56
+ --avatar-size: var(--avatar-size-lg);
57
+ --avatar-square-size: var(--avatar-size-lg);
58
+ --avatar-font-size: var(--avatar-font-size-lg);
59
+ --avatar-glyph-size: var(--avatar-glyph-size-lg);
60
+ --avatar-tinted-glyph-size: var(--avatar-glyph-size-lg);
61
+ }
62
+
63
+ .ui-avatar[data-size] svg {
64
+ inline-size: var(--avatar-glyph-size);
65
+ block-size: var(--avatar-glyph-size);
66
+ flex: none;
67
+ }
68
+
39
69
  .ui-avatar-image {
40
70
  width: 100%;
41
71
  height: 100%;
@@ -1066,8 +1096,8 @@
1066
1096
  }
1067
1097
 
1068
1098
  .ui-prose a {
1069
- color: hsl(var(--primary));
1070
- text-decoration: underline;
1099
+ color: hsl(var(--prose-link-color, var(--primary)));
1100
+ text-decoration-line: var(--prose-link-decoration-line);
1071
1101
  }
1072
1102
 
1073
1103
  .ui-prose table {
@@ -199,6 +199,18 @@
199
199
  flex-shrink: 0;
200
200
  }
201
201
 
202
+ .ui-upload-list-glyph {
203
+ display: flex;
204
+ align-items: center;
205
+ justify-content: center;
206
+ inline-size: var(--control-height-lg);
207
+ block-size: var(--control-height-lg);
208
+ border-radius: var(--radius);
209
+ background-color: hsl(var(--muted));
210
+ color: hsl(var(--muted-foreground));
211
+ flex-shrink: 0;
212
+ }
213
+
202
214
  .ui-upload-picture-item {
203
215
  inline-size: var(--upload-tile-size);
204
216
  overflow-wrap: anywhere;
@@ -336,6 +348,11 @@
336
348
  border-color: hsl(var(--primary));
337
349
  }
338
350
 
351
+ .ui-upload-picture-empty-icon {
352
+ inline-size: var(--upload-picture-icon-size);
353
+ block-size: var(--upload-picture-icon-size);
354
+ }
355
+
339
356
  .ui-upload-picture-empty-label {
340
357
  margin-block-start: var(--upload-picture-empty-label-space-block-start);
341
358
  font-size: var(--upload-picture-empty-label-font-size);
@@ -370,6 +387,11 @@
370
387
  color: hsl(var(--muted-foreground));
371
388
  }
372
389
 
390
+ .ui-upload-avatar-icon {
391
+ inline-size: var(--upload-avatar-icon-size);
392
+ block-size: var(--upload-avatar-icon-size);
393
+ }
394
+
373
395
  .ui-upload-avatar-change {
374
396
  position: absolute;
375
397
  inset-inline: 0;
@@ -0,0 +1,37 @@
1
+
2
+ @layer components {
3
+ svg.ui-icon[data-slot="icon"] {
4
+ inline-size: var(--icon-glyph-size);
5
+ block-size: var(--icon-glyph-size);
6
+
7
+ flex: 0 0 auto;
8
+ }
9
+
10
+ .ui-icon[data-size="2xs"] {
11
+ --icon-glyph-size: var(--icon-size-2xs);
12
+ }
13
+ .ui-icon[data-size="xs"] {
14
+ --icon-glyph-size: var(--icon-size-xs);
15
+ }
16
+ .ui-icon[data-size="sm"] {
17
+ --icon-glyph-size: var(--icon-size-sm);
18
+ }
19
+ .ui-icon[data-size="md"] {
20
+ --icon-glyph-size: var(--icon-size-md);
21
+ }
22
+ .ui-icon[data-size="lg"] {
23
+ --icon-glyph-size: var(--icon-size-lg);
24
+ }
25
+ .ui-icon[data-size="xl"] {
26
+ --icon-glyph-size: var(--icon-size-xl);
27
+ }
28
+ .ui-icon[data-size="2xl"] {
29
+ --icon-glyph-size: var(--icon-size-2xl);
30
+ }
31
+ .ui-icon[data-size="3xl"] {
32
+ --icon-glyph-size: var(--icon-size-3xl);
33
+ }
34
+ .ui-icon[data-size="4xl"] {
35
+ --icon-glyph-size: var(--icon-size-4xl);
36
+ }
37
+ }
@@ -25,3 +25,5 @@
25
25
  @import "./navigation-layout.css";
26
26
  @import "./chart-layout.css";
27
27
  @import "./float-button-layout.css";
28
+
29
+ @import "./icon-layout.css";
@@ -2083,6 +2083,16 @@
2083
2083
  flex: 0 0 auto;
2084
2084
  }
2085
2085
 
2086
+ .ui-topbar-item-icon {
2087
+ display: inline-flex;
2088
+ flex: 0 0 auto;
2089
+ align-items: center;
2090
+ }
2091
+ .ui-topbar-item-icon svg {
2092
+ inline-size: var(--topbar-icon-size);
2093
+ block-size: var(--topbar-icon-size);
2094
+ }
2095
+
2086
2096
  @media (width <= 68.75rem) {
2087
2097
  .ui-topbar-center {
2088
2098
  display: var(--topbar-center-compact-display);
@@ -51,37 +51,45 @@
51
51
  }
52
52
 
53
53
  [data-slot="text"][data-tone="default"],
54
- [data-slot="heading"][data-tone="default"] {
54
+ [data-slot="heading"][data-tone="default"],
55
+ [data-slot="icon"][data-tone="default"] {
55
56
  color: hsl(var(--foreground));
56
57
  }
57
58
  [data-slot="text"][data-tone="muted"],
58
- [data-slot="heading"][data-tone="muted"] {
59
+ [data-slot="heading"][data-tone="muted"],
60
+ [data-slot="icon"][data-tone="muted"] {
59
61
  color: hsl(var(--muted-foreground));
60
62
  }
61
63
 
62
64
  [data-slot="text"][data-tone="inherit"],
63
- [data-slot="heading"][data-tone="inherit"] {
65
+ [data-slot="heading"][data-tone="inherit"],
66
+ [data-slot="icon"][data-tone="inherit"] {
64
67
  color: inherit;
65
68
  }
66
69
  [data-slot="text"][data-tone="primary"],
67
- [data-slot="heading"][data-tone="primary"] {
70
+ [data-slot="heading"][data-tone="primary"],
71
+ [data-slot="icon"][data-tone="primary"] {
68
72
  color: hsl(var(--primary));
69
73
  }
70
74
 
71
75
  [data-slot="text"][data-tone="success"],
72
- [data-slot="heading"][data-tone="success"] {
76
+ [data-slot="heading"][data-tone="success"],
77
+ [data-slot="icon"][data-tone="success"] {
73
78
  color: hsl(var(--text-success));
74
79
  }
75
80
  [data-slot="text"][data-tone="warning"],
76
- [data-slot="heading"][data-tone="warning"] {
81
+ [data-slot="heading"][data-tone="warning"],
82
+ [data-slot="icon"][data-tone="warning"] {
77
83
  color: hsl(var(--text-warning));
78
84
  }
79
85
  [data-slot="text"][data-tone="destructive"],
80
- [data-slot="heading"][data-tone="destructive"] {
86
+ [data-slot="heading"][data-tone="destructive"],
87
+ [data-slot="icon"][data-tone="destructive"] {
81
88
  color: hsl(var(--text-error));
82
89
  }
83
90
  [data-slot="text"][data-tone="info"],
84
- [data-slot="heading"][data-tone="info"] {
91
+ [data-slot="heading"][data-tone="info"],
92
+ [data-slot="icon"][data-tone="info"] {
85
93
  color: hsl(var(--text-info));
86
94
  }
87
95
 
@@ -166,6 +166,7 @@
166
166
  --search-input-label-font-size: var(--font-size-xs);
167
167
  --tag-input-chip-font-size: var(--font-size-xs);
168
168
  --toggle-sm-font-size: var(--font-size-xs);
169
+ --toggle-xs-font-size: var(--font-size-xs);
169
170
  --button-sm-font-size: var(--font-size-xs);
170
171
  }
171
172
 
@@ -30,6 +30,10 @@
30
30
  --prose-code-font-size: calc(1em / var(--font-size-ratio));
31
31
  --prose-code-space-inline: var(--space-1);
32
32
  --prose-code-radius: var(--radius-sm);
33
+
34
+ --prose-link-color: initial;
35
+
36
+ --prose-link-decoration-line: underline;
33
37
  --prose-image-radius: var(--radius-md);
34
38
  --timeline-note-font-size: var(--font-size-xs);
35
39
 
@@ -37,6 +41,28 @@
37
41
 
38
42
  --avatar-tint: transparent;
39
43
 
44
+ --avatar-size: var(--control-height);
45
+
46
+ --avatar-size-xs: var(--control-height-xs);
47
+
48
+ --avatar-size-sm: var(--control-height-sm);
49
+
50
+ --avatar-size-lg: var(--control-height-lg);
51
+
52
+ --avatar-font-size: var(--font-size-base);
53
+
54
+ --avatar-font-size-xs: var(--font-size-2xs);
55
+
56
+ --avatar-font-size-sm: var(--font-size-xs);
57
+
58
+ --avatar-font-size-lg: var(--font-size-lg);
59
+
60
+ --avatar-glyph-size-xs: calc(var(--icon-size-xs) * var(--scaling));
61
+
62
+ --avatar-glyph-size-sm: calc(var(--icon-size-sm) * var(--scaling));
63
+
64
+ --avatar-glyph-size-lg: calc(var(--icon-size-lg) * var(--scaling));
65
+
40
66
  --avatar-square-radius: var(--radius-lg);
41
67
  --avatar-square-size: var(--control-height);
42
68
  --avatar-square-background: initial;
@@ -11,6 +11,10 @@
11
11
 
12
12
  --segmented-item-padding-inline: calc(var(--control-padding-x) - 1px);
13
13
 
14
+ --segmented-xs-font-size: var(--font-size-xs);
15
+
16
+ --segmented-xs-item-padding-inline: calc(var(--control-padding-x-compact) - 1px);
17
+
14
18
  --segmented-item-gap: calc(var(--space-3) / 2);
15
19
 
16
20
  --segmented-item-color: var(--muted-foreground);
@@ -30,6 +30,8 @@
30
30
  --upload-picture-radius: var(--radius-md);
31
31
  --upload-picture-preview-max-height: 12rem;
32
32
  --upload-picture-empty-height: 8rem;
33
+
34
+ --upload-picture-icon-size: var(--icon-size-2xl);
33
35
  --upload-picture-empty-label-space-block-start: var(--space-2);
34
36
  --upload-picture-empty-label-font-size: var(--font-size-sm);
35
37
  --upload-picture-overlay-font-size: var(--font-size-sm);
@@ -41,6 +43,8 @@
41
43
  --upload-picture-badge-font-size: var(--font-size-xs);
42
44
 
43
45
  --upload-avatar-size: 6rem;
46
+
47
+ --upload-avatar-icon-size: var(--icon-size-xl);
44
48
  --upload-avatar-border-width: var(--stroke-md);
45
49
  --upload-avatar-change-space-block: var(--space-1);
46
50
 
@@ -5,9 +5,9 @@ Read this once; the audit enforces it. Everything else in `docs/` is for contrib
5
5
  1. Load styles with `@import "@godxjp/ui/styles"` (fonts bundled, 729 sliced woff2 faces, ~11.7 MB), `@import "@godxjp/ui/styles/core"` (no `@font-face` at all), `@import "@godxjp/ui/styles/core-with-fallbacks"` (`core` + six `local()`-only metric-matched faces, still zero network bytes — for when you supply Noto Sans JP yourself) or `@import "@godxjp/ui/styles/core-with-jis-level1"` (`core-with-fallbacks` + Noto Sans JP merged to JIS X 0208 level 1: **3 requests, ~1.53 MB, once** instead of ~8 font requests on every navigation — for a Japanese app that wants the bundled face). Never cherry-pick `*-layout.css`.
6
6
  2. Every page is `<PageContainer title subtitle extra footer>`; its sections are spaced by the page. Group items inside a section with `<Flex direction="col" gap>` or `<ResponsiveGrid>`.
7
7
  3. No Tailwind layout on your own elements: no `flex`, `grid`, `gap-*`, `p-*`, `m-*`, `space-*`. Rows are `<Flex>` (default row), stacks are `<Flex direction="col">`, grids are `<ResponsiveGrid>`.
8
- 4. No hand-rolled surfaces: no `rounded-* border bg-*` divs. A box is `Card`, a pill is `Badge`, a person is `Avatar`, a row is `ListRow`, a label/value pair is `Descriptions`, an empty area is `EmptyState`, a read-only sample of a colour the USER chose is `Swatch`.
8
+ 4. No hand-rolled surfaces: no `rounded-* border bg-*` divs. A box is `Card`, a pill is `Badge`, a person is `Avatar`, a row is `ListRow`, a label/value pair is `Descriptions`, an empty area is `EmptyState`, a read-only sample of a colour the USER chose is `Swatch`. A LIST of those rows is `<Flex as="ul" marker="none" direction="col" gap="none">` with `<ListRow as="li">` children — `marker="none"` keeps the `<ul>`, the `<li>` semantics and the gap token while dropping the bullet and the indent. Never a raw `<ul>`/`<ol>` (it carries no gap token), never `<div role="list">` + `<div role="listitem">`, and never a wrapper around each row: the divider is `:not(:last-child)` among siblings, so a row alone in its own wrapper is always the last one and EVERY divider disappears silently. A bulleted prose list is the same `<Flex as="ul">` without `marker`.
9
9
  5. Real controls only: `Button`, `Input`, `Select`, `Textarea`, `Checkbox`… never raw `<button>`/`<input>`; a labelled control lives in `<FormField label>`. A Select outside a form takes `width="auto"`. **A disabled control's reason is visible text, never a tooltip** — see below.
10
- 6. Text is `<Text>` / `<Heading>` with `tone`, `size`, `weight`, `truncate`, `mono` — not `className="text-muted-foreground font-semibold"`.
10
+ 6. Text is `<Text>` / `<Heading>` with `tone`, `size`, `weight`, `truncate`, `mono` — not `className="text-muted-foreground font-semibold"`. A STANDALONE glyph is `<Icon as={Lock} size="sm" tone="muted" />` from `@godxjp/ui/general`: a bare lucide icon carries its own `width="24" height="24"` and only four places re-size it for you (`Button`, a `DropdownMenuItem`, a `TopbarItem`, a `ListRow` leading slot), so everywhere else — in a `Text`, a table cell, an `<a>` — it draws at 24px beside 14px type. `size-4` and `w-[16px]` are closed to you by rules 3 and 8, and `size={16}` hard-codes a number the theme owns; `Icon` puts the glyph on the `--icon-size-*` scale (`2xs…4xl`) instead, and is `aria-hidden` unless you give it a `label` (then it is `role="img"` with that name). The audit rule is `lucide-icon-needs-size`.
11
11
  7. Colours are semantic tokens (`tone="destructive"`, `bg-primary`), never palette names, hex, or `bg-black` / `text-white`.
12
12
  8. Sizes come from props (`size`, `width`, `columns`), never `w-[240px]` / `max-h-[420px]`.
13
13
  9. Logical directions (`ms-`, `me-`, `start-`, `end-`) when a utility is unavoidable; never `ml-` / `left-`.
@@ -213,6 +213,32 @@ Density (`<PageContainer density>`) scales component sizes, not the grid. - **Pi
213
213
 
214
214
  ---
215
215
 
216
+ ## Selectors the package promises are YOURS
217
+
218
+ A `data-*` attribute the package never writes is a selector you can hold a stylesheet against — the one place where CSS of your own is the documented route rather than a workaround. Each of these is a promise held by a test, not an accident of the current markup:
219
+
220
+ | You write | The package promises | Why it exists |
221
+ | --- | --- | --- |
222
+ | `.ui-prose a[data-…]` | Prose writes `data-*` on its **own root only** — never on a descendant. Every `data-*` on an `a` inside it is yours, survives the render, and stays selectable (gh#717) | A renderer knows things about a link that the package cannot: in a wiki, a link whose target **does not exist yet** must read differently from one that resolves |
223
+ | `<TableRow data-expanded-row="">` | The striping counts records, not DOM rows — a row you mark is skipped in the count and wears its record's stripe | A detail row under a record is your composition, not a row of data |
224
+ | `[data-tenant]`, `[data-program]` | Every anchor token re-resolves at the scope (below) | One app, many brands |
225
+
226
+ **Style a marked link through the knob, not around it.** `--prose-link-color` is read as `hsl(var(--prose-link-color, var(--primary)))` **at the anchor**, so declaring the custom property inside your own higher-specificity selector is enough — you never have to restate the `color` declaration, and a later package change to how the ink is painted still reaches you:
227
+
228
+ ```css
229
+ /* your app's stylesheet */
230
+ .ui-prose a[data-unresolved="true"] {
231
+ --prose-link-color: var(--text-error); /* the TEXT tier — 7.25:1 light, 5.51:1 dark on a card */
232
+ text-decoration-style: dashed;
233
+ }
234
+ ```
235
+
236
+ Use `--text-error`, not `--destructive`: the fill tier is tuned for a white label on top of it and measures 2.95:1 as ink on the dark card (gh#610). The whole family is `--prose-link-color` (default `hsl(var(--primary))`, resolved at the anchor so a scoped re-tint reaches it) and `--prose-link-decoration-line` (default `underline`). Set the first on `[data-tenant]` to re-tint every wiki link at once.
237
+
238
+ There is **no prop** naming the attribute, deliberately: the attribute IS the API, the same way `data-expanded-row` and `data-axe-open` are. A prop would make the package own a name only your renderer knows, and would let exactly one state be marked.
239
+
240
+ ---
241
+
216
242
  ## Multi-tenant (one app, many brands)
217
243
 
218
244
  Scope the overrides under a tenant attribute instead of `:root`. The colour utilities are declared with `@theme inline`, so a scoped `--primary` (and the other anchors) **re-resolve at the element** — `bg-primary`, `text-success`, every component surface, the focus ring, the brand glow, gradients and the modal scrim all retint inside the scope:
@@ -3,8 +3,8 @@
3
3
  > Generated by `scripts/frame-coverage.mjs` (issue #163). Do not edit by hand — run `pnpm check:frame-coverage`.
4
4
  > Standard: [FRAME-COVERAGE-STANDARD.md](./FRAME-COVERAGE-STANDARD.md). A missing dimension is **UNTESTED**, never a pass.
5
5
 
6
- - Public components: **163**
7
- - With ≥1 frame: **142** · zero-frame: **21**
6
+ - Public components: **164**
7
+ - With ≥1 frame: **143** · zero-frame: **21**
8
8
  - Fully covered (every contract axis declared): **0**
9
9
 
10
10
  ## UNTESTED — no `/frame/**` route
@@ -74,6 +74,7 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
74
74
  | Logo | ✓ | ✓ | N/A | N/A | · | · | · | · | · | · |
75
75
  | Reveal | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
76
76
  | Activity | ✓ | · | N/A | N/A | · | · | · | · | · | · |
77
+ | Icon | ✓ | · | N/A | N/A | · | · | · | · | · | · |
77
78
  | DataTable | ✓ | ✓ | · | · | · | · | · | · | · | · |
78
79
  | Card | ✓ | ✓ | N/A | N/A | · | · | · | · | · | · |
79
80
  | CardContent | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |