@microbit/ui 0.1.0-alpha.15 → 0.1.0-alpha.16

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": "@microbit/ui",
3
- "version": "0.1.0-alpha.15",
3
+ "version": "0.1.0-alpha.16",
4
4
  "description": "micro:bit design-system primitives: react-aria-components + Panda CSS with a design language ported from Chakra UI v2. Ships as source; see README for the consumption setup.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -38,7 +38,19 @@ export const gridList = defineSlotRecipe({
38
38
  transitionDuration: "ultra-fast",
39
39
  transitionTimingFunction: "ease-in",
40
40
  _hover: { bg: "gray.50" },
41
- "&[data-selected]": { bg: "gray.100", _hover: { bg: "gray.100" } },
41
+ // A row holding an open menu (or any other popover) keeps the hover
42
+ // grey, so the row an open menu belongs to stays visible. Hover state
43
+ // cannot do this on its own: a Popover lays a fixed full-viewport
44
+ // underlay over the page while open, which takes the pointer off the
45
+ // row — `:hover` and RAC's own `data-hovered` both drop the moment the
46
+ // menu appears. A trigger carries `aria-expanded` (useOverlayTrigger),
47
+ // so the row can see its own open overlay.
48
+ "&:has([aria-expanded=true])": { bg: "gray.50" },
49
+ "&[data-selected]": {
50
+ bg: "gray.100",
51
+ _hover: { bg: "gray.100" },
52
+ "&:has([aria-expanded=true])": { bg: "gray.100" },
53
+ },
42
54
  "&[data-focus-visible]": { focusShadow: "outline" },
43
55
  "&[data-disabled]": { opacity: 0.4, cursor: "not-allowed" },
44
56
  },
@@ -72,14 +72,38 @@ export const select = defineSlotRecipe({
72
72
  color: "inherit",
73
73
  h: "10",
74
74
  px: "4",
75
+ // As the input recipe, so a Select, a NativeSelect and a TextField in one
76
+ // form all tint together on hover. (react-aria's TextField has no hover
77
+ // effect, but matching the family beats matching their docs.)
75
78
  _hover: { borderColor: "gray.300" },
76
- "&[data-focus-visible]": {
77
- focusShadow: "outline",
79
+ // `data-invalid` lands on the root — and, in a ComboBox, on the input —
80
+ // but never on the trigger: a RAC Button has no validity state, and our
81
+ // ComboBox control is a plain div. So it comes down from the parent.
82
+ // `> &` rather than a descendant selector, so an app's own invalid form
83
+ // wrapper cannot paint every control inside it red.
84
+ //
85
+ // Declared after hover and before focus so red beats a hover tint and
86
+ // the focus ring beats red, as in the input recipe.
87
+ "[data-invalid] > &": {
88
+ borderColor: "danger.500",
89
+ boxShadow: "0 0 0 1px token(colors.danger.500)",
90
+ },
91
+ // Two focus cases. `data-focus-visible` is Select's button on keyboard
92
+ // focus only (RAC leaves it unset for mouse, matching the react-aria
93
+ // docs' Select). The `:has()` arm is ComboBox: its control is a plain
94
+ // div wrapping an input, so it gets no RAC attributes itself, and as a
95
+ // text field it should show focus on any modality. That arm watches
96
+ // native `:focus` rather than the input's `data-focused`, because
97
+ // react-aria dispatches a synthetic blur at the input whenever virtual
98
+ // focus moves to an option (aria-activedescendant) — which strips RAC's
99
+ // attribute for as long as the list has an active option, real focus
100
+ // never having left. Select's trigger holds no input, so it can't match.
101
+ "&[data-focus-visible], &:has(input:focus)": {
102
+ boxShadow: "0 0 0 1px token(colors.focusBorder)",
78
103
  borderColor: "focusBorder",
104
+ outline: "2px solid transparent",
105
+ outlineOffset: "2px",
79
106
  },
80
- // A ComboBox's control is an input, which is focused whenever it is open.
81
- "&[data-focused]": { focusShadow: "outline", borderColor: "focusBorder" },
82
- "&[data-invalid]": { borderColor: "danger.500" },
83
107
  "&[data-disabled]": { opacity: 0.4, cursor: "not-allowed" },
84
108
  },
85
109
  // Whatever shows the current value: Select's SelectValue, ComboBox's
@@ -113,8 +137,11 @@ export const select = defineSlotRecipe({
113
137
  background: "transparent",
114
138
  border: "none",
115
139
  cursor: "pointer",
140
+ // No focus styling, deliberately: react-aria keeps a ComboBox's toggle
141
+ // button out of the tab order (the input owns the keyboard), so a ring
142
+ // here would only ever be reachable programmatically, and would suggest
143
+ // the chevron is a tab stop. The whole control shows focus instead.
116
144
  outline: "none",
117
- "&[data-focus-visible]": { focusShadow: "outline" },
118
145
  },
119
146
  content: {
120
147
  // Line the card up with the control, as a select should and as
@@ -122,6 +149,8 @@ export const select = defineSlotRecipe({
122
149
  // button it measures; `ComboBox` measures its own control and sets the
123
150
  // width inline, because RAC's var is the *input's* width there.
124
151
  minWidth: "var(--trigger-width)",
152
+ display: "flex",
153
+ flexDirection: "column",
125
154
  bg: "white",
126
155
  color: "inherit",
127
156
  py: "2",
@@ -141,7 +170,6 @@ export const select = defineSlotRecipe({
141
170
  },
142
171
  list: {
143
172
  outline: "none",
144
- maxHeight: "inherit",
145
173
  overflowY: "auto",
146
174
  },
147
175
  option: {