@astryxdesign/core 0.5.2-canary.d7c6772 → 0.5.2-canary.dc5565e

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.
@@ -1 +1 @@
1
- {"version":3,"file":"Field.d.ts","sourceRoot":"","sources":["../../src/Field/Field.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAC,KAAK,SAAS,EAAM,MAAM,OAAO,CAAC;AAE1C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAG9C,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,4BAA4B,CAAC;AAEnE,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,SAAS,CAAC;AAqCtC,YAAY,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAE9C,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAE9D,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,IAAI,EAAE,eAAe,CAAC;IACtB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAW,SAAQ,IAAI,CACtC,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,CACX;IACC,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,SAAS,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IACjC;;;OAGG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,KAAK,CAAC,EACpB,KAAK,EACL,aAAqB,EACrB,WAAW,EACX,OAAO,EACP,OAAO,EACP,YAAoB,EACpB,aAAa,EACb,UAAkB,EAClB,UAAkB,EAClB,UAAkB,EAClB,SAAS,EACT,MAAM,EACN,YAAY,EACZ,aAA0B,EAC1B,KAAK,EACL,MAAM,EACN,QAAQ,EACR,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,UAAU,+BAwGZ;yBA9He,KAAK"}
1
+ {"version":3,"file":"Field.d.ts","sourceRoot":"","sources":["../../src/Field/Field.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAC,KAAK,SAAS,EAAM,MAAM,OAAO,CAAC;AAE1C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAG9C,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,4BAA4B,CAAC;AAEnE,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,SAAS,CAAC;AAmDtC,YAAY,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAE9C,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAE9D,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,IAAI,EAAE,eAAe,CAAC;IACtB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAW,SAAQ,IAAI,CACtC,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,CACX;IACC,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,SAAS,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IACjC;;;OAGG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,KAAK,CAAC,EACpB,KAAK,EACL,aAAqB,EACrB,WAAW,EACX,OAAO,EACP,OAAO,EACP,YAAoB,EACpB,aAAa,EACb,UAAkB,EAClB,UAAkB,EAClB,UAAkB,EAClB,SAAS,EACT,MAAM,EACN,YAAY,EACZ,aAA0B,EAC1B,KAAK,EACL,MAAM,EACN,QAAQ,EACR,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,UAAU,+BA2GZ;yBAjIe,KAAK"}
@@ -20,7 +20,7 @@ import * as stylex from '@stylexjs/stylex';
20
20
  import { FieldLabel } from "./FieldLabel.js";
21
21
  import { FieldStatus } from "../FieldStatus/FieldStatus.js";
22
22
  import "../theme/tokens.stylex.js";
23
- import { spacingVars, borderVars } from "../theme/tokens.stylex.js";
23
+ import { spacingVars, borderVars, sizeVars } from "../theme/tokens.stylex.js";
24
24
  import { mergeProps } from "../utils/index.js";
25
25
  import { useDevWarning } from "../hooks/useDevWarning.js";
26
26
  import { FormLayoutContext } from "../FormLayout/FormLayoutContext.js";
@@ -40,6 +40,11 @@ const styles = {
40
40
  horizontalLabels: {
41
41
  k1xSpc: "xjp7ctv",
42
42
  $$css: true
43
+ },
44
+ attachedStatusLayer: {
45
+ kVAEAm: "x1n2onr6",
46
+ kY2c9j: "x8knxv4",
47
+ $$css: true
43
48
  }
44
49
  };
45
50
 
@@ -116,7 +121,8 @@ export function Field({
116
121
  type: status.type,
117
122
  message: status.message,
118
123
  id: resolvedMessageID,
119
- variant: statusVariant
124
+ variant: statusVariant,
125
+ xstyle: statusVariant === 'attached' ? styles.attachedStatusLayer : undefined
120
126
  }) : null;
121
127
 
122
128
  // ─── Horizontal-labels mode ───────────────────────────────────────────
@@ -138,7 +144,7 @@ export function Field({
138
144
  children: labelNode
139
145
  }), /*#__PURE__*/_jsxs("div", {
140
146
  ...{
141
- className: "x78zum5 xdt5ytf xc8icb0"
147
+ className: "x78zum5 xdt5ytf xc8icb0 x19nvjgn xlumz0n x1qs65gq"
142
148
  },
143
149
  children: [description && /*#__PURE__*/_jsx(Text, {
144
150
  type: "supporting",
@@ -157,7 +163,7 @@ export function Field({
157
163
  ...props,
158
164
  children: [labelNode, statusVariant === 'attached' ? /*#__PURE__*/_jsxs("div", {
159
165
  ...{
160
- className: "x78zum5 xdt5ytf xc8icb0"
166
+ className: "x78zum5 xdt5ytf xc8icb0 x19nvjgn xlumz0n x1qs65gq"
161
167
  },
162
168
  children: [children, statusNode]
163
169
  }) : /*#__PURE__*/_jsxs(_Fragment, {
@@ -1 +1 @@
1
- {"version":3,"file":"FieldStatus.d.ts","sourceRoot":"","sources":["../../src/FieldStatus/FieldStatus.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAU5C,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAC;AAKpD,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,SAAS,CAAC;AAgEnD;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,qBAAqB,CAAC;AAE7D,MAAM,WAAW,gBAAiB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACjE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;OAEG;IACH,IAAI,EAAE,eAAe,CAAC;IACtB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,WAAW,CAAC,EAC1B,GAAG,EACH,IAAI,EACJ,OAAO,EACP,EAAE,EACF,OAAoB,EACpB,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,IAAI,EACR,EAAE,gBAAgB,qBAsDlB;yBAhEe,WAAW"}
1
+ {"version":3,"file":"FieldStatus.d.ts","sourceRoot":"","sources":["../../src/FieldStatus/FieldStatus.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAU5C,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAC;AAKpD,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,SAAS,CAAC;AAqEnD;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,qBAAqB,CAAC;AAE7D,MAAM,WAAW,gBAAiB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACjE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;OAEG;IACH,IAAI,EAAE,eAAe,CAAC;IACtB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,WAAW,CAAC,EAC1B,GAAG,EACH,IAAI,EACJ,OAAO,EACP,EAAE,EACF,OAAoB,EACpB,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,IAAI,EACR,EAAE,gBAAgB,qBAsDlB;yBAhEe,WAAW"}
@@ -36,6 +36,7 @@ const statusIconMap = {
36
36
  error: 'error',
37
37
  success: 'success'
38
38
  };
39
+ const ATTACHED_OVERLAP = `var(--_field-status-overlap, ${spacingVars['--spacing-1-5']})`;
39
40
  const styles = {
40
41
  base: {
41
42
  kMv6JI: "x9ynric",
@@ -44,12 +45,13 @@ const styles = {
44
45
  $$css: true
45
46
  },
46
47
  attached: {
47
- keoZOQ: "x1c40v9y",
48
- kLKAdn: "x1ak3mig",
48
+ keoZOQ: "x3dbumh",
49
+ kLKAdn: "xdgtoc0",
49
50
  kGO01o: "x1wesfrj",
50
51
  kg3NbH: "xf314gf",
51
52
  kVL7Gh: "xquck67",
52
53
  kT0f0o: "x14i3lts",
54
+ kfzvcC: "x47corl",
53
55
  $$css: true
54
56
  },
55
57
  detached: {
package/dist/astryx.css CHANGED
@@ -114,6 +114,7 @@
114
114
  .x1fcsqxe{--_dropdown-menu-radius:var(--radius-container)}
115
115
  .x13l1x9d{--_fade-delay:0s}
116
116
  .x1832zxr{--_field-radius:var(--radius-element)}
117
+ .x19nvjgn{--_field-status-overlap:calc(var(--size-element-md) / 2)}
117
118
  .xgzxwq1{--_focus-outline-offset:0}
118
119
  .x17wzz1v{--_focus-outline:none}
119
120
  .xhg274n{--_hover-delay:0s}
@@ -352,6 +353,8 @@
352
353
  .xoa21yc:focus-within{--_reveal-position:static}
353
354
  .x11woleg:focus-visible{--indicator-color:var(--color-accent)}
354
355
  .x1my2cv6.x1my2cv6:hover:where(:not(:disabled,[aria-disabled="true"])){--indicator-color:var(--color-accent)}
356
+ .x1qs65gq:has(> [data-size="lg"]){--_field-status-overlap:calc(var(--size-element-lg) / 2)}
357
+ .xlumz0n:has(> [data-size="sm"]){--_field-status-overlap:calc(var(--size-element-sm) / 2)}
355
358
  .x158n88w:has(:focus-visible){--_tab-strip-bleed:calc(var(--focus-outline-width) + var(--focus-outline-offset))}
356
359
  .x1ibt0lz:first-child{--separator-display:none}
357
360
  .x1rht92:nth-child(even){--table-row-overlay:var(--color-background-muted)}
@@ -1295,6 +1298,7 @@
1295
1298
  .x1yn0g08:not(#\#):not(#\#):not(#\#){word-break:break-all}
1296
1299
  .x13faqbe:not(#\#):not(#\#):not(#\#){word-break:break-word}
1297
1300
  .x1lldw8n:not(#\#):not(#\#):not(#\#){word-break:normal}
1301
+ .x8knxv4:not(#\#):not(#\#):not(#\#){z-index:-1}
1298
1302
  .x1ja2u2z:not(#\#):not(#\#):not(#\#){z-index:0}
1299
1303
  .x1vjfegm:not(#\#):not(#\#):not(#\#){z-index:1}
1300
1304
  .xfo81ep:not(#\#):not(#\#):not(#\#){z-index:1000}
@@ -1589,6 +1593,7 @@
1589
1593
  .xmqyaqf:not(#\#):not(#\#):not(#\#):not(#\#){margin-top:calc(-1 * (var(--_tab-strip-bleed)))}
1590
1594
  .xvyqdj1:not(#\#):not(#\#):not(#\#):not(#\#){margin-top:calc(-1 * anchor-size(height) - 8px)}
1591
1595
  .xckg3cf:not(#\#):not(#\#):not(#\#):not(#\#){margin-top:calc(-1 * var(--_chat-composer-radius))}
1596
+ .x3dbumh:not(#\#):not(#\#):not(#\#):not(#\#){margin-top:calc(-1 * var(--_field-status-overlap,var(--spacing-1-5)))}
1592
1597
  .x5j3pgv:not(#\#):not(#\#):not(#\#):not(#\#){margin-top:calc(-1 * var(--astryx-number-input-padding-block-start,var(--astryx-number-input-padding,var(--spacing-1))))}
1593
1598
  .x1sa9bsh:not(#\#):not(#\#):not(#\#):not(#\#){margin-top:calc(-1 * var(--container-padding-block-start,0px))}
1594
1599
  .x1c40v9y:not(#\#):not(#\#):not(#\#):not(#\#){margin-top:calc(-1 * var(--spacing-1-5))}
@@ -1715,9 +1720,9 @@
1715
1720
  .x2oz4g1:not(#\#):not(#\#):not(#\#):not(#\#){padding-bottom:var(--spacing-8)}
1716
1721
  .xexx8yu:not(#\#):not(#\#):not(#\#):not(#\#){padding-top:0}
1717
1722
  .x1htvmrp:not(#\#):not(#\#):not(#\#):not(#\#){padding-top:calc(var(--_chat-composer-padding) + var(--_chat-composer-radius))}
1723
+ .xdgtoc0:not(#\#):not(#\#):not(#\#):not(#\#){padding-top:calc(var(--_field-status-overlap,var(--spacing-1-5)) + var(--spacing-2))}
1718
1724
  .x1enrzb7:not(#\#):not(#\#):not(#\#):not(#\#){padding-top:calc(var(--border-width) + var(--spacing-1))}
1719
1725
  .x1nex4ik:not(#\#):not(#\#):not(#\#):not(#\#){padding-top:calc(var(--container-padding-block-start) - var(--border-width))}
1720
- .x1ak3mig:not(#\#):not(#\#):not(#\#):not(#\#){padding-top:calc(var(--spacing-1-5) + var(--spacing-2))}
1721
1726
  .x1vjezoa:not(#\#):not(#\#):not(#\#):not(#\#){padding-top:max(var(--spacing-4),env(safe-area-inset-top,0px))}
1722
1727
  .x2gtn7z:not(#\#):not(#\#):not(#\#):not(#\#){padding-top:var(--_chat-composer-padding)}
1723
1728
  .xlr2r7p:not(#\#):not(#\#):not(#\#):not(#\#){padding-top:var(--_tab-strip-bleed)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/core",
3
- "version": "0.5.2-canary.d7c6772",
3
+ "version": "0.5.2-canary.dc5565e",
4
4
  "displayName": "Astryx Core",
5
5
  "description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
6
6
  "author": "Meta Open Source",
@@ -661,7 +661,7 @@
661
661
  "react-dom": ">=19.0.0"
662
662
  },
663
663
  "devDependencies": {
664
- "@astryxdesign/cli": "0.5.2-canary.d7c6772",
664
+ "@astryxdesign/cli": "0.5.2-canary.dc5565e",
665
665
  "@babel/cli": "^8.0.4",
666
666
  "@babel/core": "^7.29.7",
667
667
  "@babel/preset-react": "^8.0.1",
@@ -18,7 +18,7 @@ verified_by:
18
18
  scripts/check-knowledge.mjs,
19
19
  ]
20
20
  modules: []
21
- families: [family:overlay-dismissal]
21
+ families: [family:input-fields, family:overlay-dismissal]
22
22
  design_specs: []
23
23
  architecture:
24
24
  [architecture:component-theming-surface, architecture:layer-runtime]
@@ -39,6 +39,7 @@ export const docs = {
39
39
  ],
40
40
  vars: [
41
41
  {name: '--_field-radius', description: 'Border radius of input fields', default: 'var(--radius-element)', private: true},
42
+ {name: '--_field-status-overlap', description: 'Amount an attached FieldStatus extends behind the lower half of the control. Set from the rendered control size.', default: 'calc(var(--size-element-md) / 2)', private: true},
42
43
  {name: '--_input-clear-hit-inset', description: 'Outset of the clear (\u2715) button\'s invisible hit area, applied to a ::after overlay. 0 on a fine pointer; negative on a coarse one, which grows the 20px button to the 24px touch target without changing what is drawn.', default: '0px', private: true},
43
44
  {name: '--_input-clear-hit-content', description: 'Whether the clear (\u2715) button\'s invisible hit overlay exists. `none` on a fine pointer, so no ::after is generated and hover still reaches the glyph; `""` on a coarse one, where the overlay provides the 24px touch target.', default: 'none', private: true},
44
45
  ],
@@ -27,6 +27,45 @@ afterEach(() => {
27
27
  });
28
28
 
29
29
  describe('Field', () => {
30
+ it.each(['sm', 'md', 'lg'] as const)(
31
+ 'provides a half-height overlap for an attached %s control',
32
+ size => {
33
+ render(
34
+ <Field
35
+ label="Username"
36
+ inputID="username"
37
+ status={{type: 'warning', message: 'This username may be taken'}}>
38
+ <div data-size={size}>Control</div>
39
+ </Field>,
40
+ );
41
+
42
+ const status = screen.getByText('This username may be taken');
43
+ expect(
44
+ getComputedStyle(status.parentElement!).getPropertyValue(
45
+ '--_field-status-overlap',
46
+ ),
47
+ ).toBe(`calc(var(--size-element-${size}) / 2)`);
48
+ },
49
+ );
50
+
51
+ it('layers an attached status behind a custom control', () => {
52
+ render(
53
+ <Field
54
+ label="Username"
55
+ inputID="username"
56
+ status={{type: 'warning', message: 'This username may be taken'}}>
57
+ <input id="username" />
58
+ </Field>,
59
+ );
60
+
61
+ const status = screen.getByText('This username may be taken');
62
+ const wrapper = status.parentElement!;
63
+
64
+ expect(getComputedStyle(wrapper).isolation).toBe('isolate');
65
+ expect(getComputedStyle(status).position).toBe('relative');
66
+ expect(getComputedStyle(status).zIndex).toBe('-1');
67
+ });
68
+
30
69
  it('renders with label', () => {
31
70
  render(
32
71
  <Field label="Email" inputID="email-input">
@@ -23,7 +23,7 @@ import type {SizeValue} from '../utils/types';
23
23
  import {FieldLabel} from './FieldLabel';
24
24
  import {FieldStatus} from '../FieldStatus/FieldStatus';
25
25
  import type {FieldStatusVariant} from '../FieldStatus/FieldStatus';
26
- import {spacingVars, borderVars} from '../theme/tokens.stylex';
26
+ import {spacingVars, borderVars, sizeVars} from '../theme/tokens.stylex';
27
27
  import type {IconType} from '../Icon';
28
28
  import {mergeProps} from '../utils';
29
29
  import {useDevWarning} from '../hooks/useDevWarning';
@@ -52,6 +52,20 @@ const styles = stylex.create({
52
52
  display: 'flex',
53
53
  flexDirection: 'column',
54
54
  isolation: 'isolate',
55
+ // Extend an attached FieldStatus behind the lower half of the control.
56
+ // Half-height is the maximum effective corner radius CSS can render, even
57
+ // when a theme uses a pill value such as --radius-full (9999px).
58
+ '--_field-status-overlap': {
59
+ default: `calc(${sizeVars['--size-element-md']} / 2)`,
60
+ ':has(> [data-size="sm"])': `calc(${sizeVars['--size-element-sm']} / 2)`,
61
+ ':has(> [data-size="lg"])': `calc(${sizeVars['--size-element-lg']} / 2)`,
62
+ },
63
+ },
64
+ attachedStatusLayer: {
65
+ // Keep the overlapping background below both Astryx inputs and custom
66
+ // controls. The isolated wrapper contains this negative stacking layer.
67
+ position: 'relative',
68
+ zIndex: -1,
55
69
  },
56
70
  });
57
71
 
@@ -250,6 +264,9 @@ export function Field({
250
264
  message={status.message}
251
265
  id={resolvedMessageID}
252
266
  variant={statusVariant}
267
+ xstyle={
268
+ statusVariant === 'attached' ? styles.attachedStatusLayer : undefined
269
+ }
253
270
  />
254
271
  ) : null;
255
272
 
@@ -149,6 +149,23 @@ describe('FieldStatus', () => {
149
149
  expect(el).toHaveAttribute('data-variant', 'attached');
150
150
  expect(el).toHaveClass('attached');
151
151
  });
152
+
153
+ it('extends the attached background by the field-provided overlap', () => {
154
+ render(<FieldStatus type="warning" message="msg" data-testid="fs" />);
155
+ const styles = getComputedStyle(screen.getByTestId('fs'));
156
+
157
+ expect(styles.marginTop).toBe(
158
+ 'calc(-1 * var(--_field-status-overlap,var(--spacing-1-5)))',
159
+ );
160
+ });
161
+
162
+ it('does not intercept pointer input over the attached control', () => {
163
+ render(<FieldStatus type="warning" message="msg" data-testid="fs" />);
164
+
165
+ expect(getComputedStyle(screen.getByTestId('fs')).pointerEvents).toBe(
166
+ 'none',
167
+ );
168
+ });
152
169
  });
153
170
 
154
171
  describe('color styling per status type', () => {
@@ -46,6 +46,8 @@ const statusIconMap: Record<InputStatusType, IconName> = {
46
46
  success: 'success',
47
47
  };
48
48
 
49
+ const ATTACHED_OVERLAP = `var(--_field-status-overlap, ${spacingVars['--spacing-1-5']})`;
50
+
49
51
  const styles = stylex.create({
50
52
  base: {
51
53
  fontFamily: typographyVars['--font-family-body'],
@@ -53,12 +55,15 @@ const styles = stylex.create({
53
55
  lineHeight: typeScaleVars['--text-supporting-leading'],
54
56
  },
55
57
  attached: {
56
- marginTop: `calc(-1 * ${spacingVars['--spacing-1-5']})`,
57
- paddingBlockStart: `calc(${spacingVars['--spacing-1-5']} + ${spacingVars['--spacing-2']})`,
58
+ marginTop: `calc(-1 * ${ATTACHED_OVERLAP})`,
59
+ paddingBlockStart: `calc(${ATTACHED_OVERLAP} + ${spacingVars['--spacing-2']})`,
58
60
  paddingBlockEnd: spacingVars['--spacing-2'],
59
61
  paddingInline: spacingVars['--spacing-2'],
60
62
  borderEndStartRadius: radiusVars['--radius-element'],
61
63
  borderEndEndRadius: radiusVars['--radius-element'],
64
+ // The overlap is visual only. Let pointer input reach the control beneath
65
+ // it instead of allowing the later-painted status box to steal the click.
66
+ pointerEvents: 'none',
62
67
  },
63
68
  detached: {
64
69
  marginTop: spacingVars['--spacing-1'],
@@ -8,6 +8,15 @@ export const docs = {
8
8
  displayName: 'Layout Header',
9
9
  isHiddenFromOverview: true,
10
10
  description: 'Top bar for page titles, app bars, and toolbars.',
11
+ playground: {
12
+ defaults: {
13
+ children: 'Page Title',
14
+ hasDivider: true,
15
+ },
16
+ wrapper: {
17
+ component: 'Layout',
18
+ },
19
+ },
11
20
  props: [
12
21
  {
13
22
  name: 'children',
@@ -22,7 +22,7 @@ verified_by:
22
22
  scripts/check-knowledge.mjs,
23
23
  ]
24
24
  modules: []
25
- families: [family:overlay-dismissal]
25
+ families: [family:input-fields, family:overlay-dismissal]
26
26
  design_specs: []
27
27
  architecture:
28
28
  [
@@ -16,7 +16,7 @@ verified_by:
16
16
  packages/core/src/Selector/Selector.source-build.test.mjs,
17
17
  ]
18
18
  modules: []
19
- families: []
19
+ families: [family:input-fields]
20
20
  design_specs: []
21
21
  architecture:
22
22
  [
@@ -71,8 +71,9 @@ connection between the closed trigger and its selection surface.
71
71
  - Multi-value selection; MultiSelector owns it.
72
72
  - Product-specific option content or filtering performed outside the supplied
73
73
  option list.
74
- - Family-wide input sizing, loading, end-lane, or disabled-reason policy. The
75
- draft `family:input-fields` record is non-authoritative context only.
74
+ - Family-wide input state display, behavior, appearance, size, end-control,
75
+ status-placement, and disabled-reason policy — owned by current
76
+ `family:input-fields`.
76
77
 
77
78
  ## Public concepts
78
79
 
@@ -266,9 +267,10 @@ in `spec:AST-004` as current runtime behavior.
266
267
  has changed.
267
268
  - `spec:AST-011/DEC-1` owns the additive read-only state: caller-owned policy can
268
269
  preserve a focusable, submittable value without exposing selection controls.
269
- - `family:input-fields` remains a draft. It may inform future reconciliation, but
270
- this current component contract neither backlinks to it in frontmatter nor
271
- depends on it for present policy.
270
+ - `family:input-fields` owns family-wide state display, behavior, appearance,
271
+ size, end-control, disabled-reason, and status-placement policy. Selector
272
+ keeps its DEC-1 standalone inline-size exception; this component contract
273
+ owns selection behavior inside that boundary.
272
274
 
273
275
  ## Verification map
274
276
 
@@ -17,7 +17,7 @@ verified_by:
17
17
  scripts/check-knowledge.mjs,
18
18
  ]
19
19
  modules: []
20
- families: []
20
+ families: [family:input-fields]
21
21
  design_specs: []
22
22
  architecture: [architecture:component-theming-surface]
23
23
  contributing: []
@@ -18,7 +18,7 @@ verified_by:
18
18
  scripts/check-knowledge.mjs,
19
19
  ]
20
20
  modules: []
21
- families: [family:overlay-dismissal]
21
+ families: [family:input-fields, family:overlay-dismissal]
22
22
  design_specs: []
23
23
  architecture:
24
24
  [
@@ -222,10 +222,10 @@ This contract adds no target, state, or visual styling contract.
222
222
  geometry and package-peer identity out of Tokenizer's public API.
223
223
  - The current Typeahead draft records BaseTypeahead's layer delegation but does
224
224
  not own Tokenizer's token/input identity or the approved descriptor.
225
- - The input-field contract at
226
- [#5778](https://github.com/facebook/astryx/pull/5778) is unlanded draft evidence
227
- that Tokenizer owns its component-local DOM/CSS mechanism. It is not authority
228
- for this branch and is intentionally absent from frontmatter.
225
+ - `family:input-fields` owns family-wide state display, behavior, appearance,
226
+ size, end-control, disabled-reason, and status-placement policy. Tokenizer
227
+ keeps its component-local token/input and source-loading behavior inside that
228
+ boundary.
229
229
  - The PowerSearch contract at
230
230
  [#5804](https://github.com/facebook/astryx/pull/5804) depends on the approved
231
231
  descriptor and owns its own editor width policy. It is dependent draft evidence,
@@ -20,7 +20,7 @@ verified_by:
20
20
  scripts/check-knowledge.mjs,
21
21
  ]
22
22
  modules: []
23
- families: [family:overlay-dismissal]
23
+ families: [family:input-fields, family:overlay-dismissal]
24
24
  design_specs: []
25
25
  architecture:
26
26
  [architecture:component-theming-surface, architecture:layer-runtime]
@@ -272,6 +272,7 @@ const VARS_WITHOUT_DERIVED_MAPPING = new Set([
272
272
  '--button-focus-offset',
273
273
  '--button-icon-only-aspect',
274
274
  '--_avatar-group-overlap',
275
+ '--_field-status-overlap',
275
276
  '--_codeblock-gutter-width',
276
277
  '--_tab-indicator-bottom',
277
278
  // Hit-area outset on a ::after overlay, and whether that overlay is