Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ jobs:
- run: npm run lint
- run: npm run typecheck
- run: npm test
- run: npx playwright install --with-deps chromium
- run: npm run test:browser
- run: npm run build
- run: npm run pack:check
- run: npm run verify:docs
39 changes: 27 additions & 12 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
fetch-depth: 0
persist-credentials: true

- name: Prepare version tag
- name: Resolve release version
id: release
shell: bash
run: |
Expand All @@ -35,16 +35,16 @@ jobs:
echo "::error::Tag ${GITHUB_REF_NAME} does not match package version ${package_version}."
exit 1
fi
echo "publish=true" >> "$GITHUB_OUTPUT"
elif git ls-remote --exit-code --tags origin "refs/tags/${expected_tag}" >/dev/null 2>&1; then
echo "::notice::${expected_tag} already exists; no release is needed."
echo "publish=false" >> "$GITHUB_OUTPUT"
echo "validate=false" >> "$GITHUB_OUTPUT"
exit 0
else
git tag "${expected_tag}" "${GITHUB_SHA}"
git push origin "refs/tags/${expected_tag}"
echo "publish=false" >> "$GITHUB_OUTPUT"
fi

echo "publish=true" >> "$GITHUB_OUTPUT"
echo "validate=true" >> "$GITHUB_OUTPUT"
echo "tag=${expected_tag}" >> "$GITHUB_OUTPUT"

- name: Verify tag is on main
Expand All @@ -61,30 +61,45 @@ jobs:
fi

- uses: actions/setup-node@v7
if: steps.release.outputs.publish == 'true'
if: steps.release.outputs.validate == 'true'
with:
node-version: 24
registry-url: https://registry.npmjs.org
package-manager-cache: false

- name: Ensure Trusted Publishing-capable npm
if: steps.release.outputs.publish == 'true'
if: steps.release.outputs.validate == 'true'
run: npm install --global "npm@^11.15.0"

- if: steps.release.outputs.publish == 'true'
- if: steps.release.outputs.validate == 'true'
run: npm ci
- if: steps.release.outputs.publish == 'true'
- if: steps.release.outputs.validate == 'true'
run: npm run check
- if: steps.release.outputs.publish == 'true'
- if: steps.release.outputs.validate == 'true'
run: npx playwright install --with-deps chromium
- if: steps.release.outputs.validate == 'true'
run: npm run test:browser
- if: steps.release.outputs.validate == 'true'
run: npm run build
- if: steps.release.outputs.publish == 'true'
- if: steps.release.outputs.validate == 'true'
run: npm run pack:check
- if: steps.release.outputs.validate == 'true'
run: npm run verify:docs

- name: Verify package is publishable
if: steps.release.outputs.publish == 'true'
if: steps.release.outputs.validate == 'true'
run: |
node -e 'const p = require("./package.json"); if (p.private) { console.error("package.json still has private:true; remove it before the npm release."); process.exit(1); }'

- name: Create validated release tag
if: steps.release.outputs.validate == 'true' && steps.release.outputs.publish != 'true'
shell: bash
env:
RELEASE_TAG: ${{ steps.release.outputs.tag }}
run: |
git tag "${RELEASE_TAG}" "${GITHUB_SHA}"
git push origin "refs/tags/${RELEASE_TAG}"

- name: Publish to npm
if: steps.release.outputs.publish == 'true'
run: npm publish --provenance --access public
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

## Unreleased

## 0.6.0 — 2026-09-01

- Add container-aware Window chrome with `chrome="auto | floating | stacked"` and explicit
`layout="auto | inline | stacked"` overrides for in-body headers and field action rows.
- Give the Window frame one geometry owner so title, menu, content, and status rows share aligned
edges across active, inactive, collapsed, floating, and stacked states.
- Give `Window.MenuBar` Base UI menubar semantics, including coordinated keyboard focus and
open-menu handoff, and add Menu link, checkbox, radio, group, and submenu parts.
- Match Select, Combobox, and Autocomplete popups to their anchor by default, expose positioning
overrides, and add explicit item text/indicator geometry.
- Add container-width regression coverage and make documentation grids adapt to their containing
section instead of viewport-only breakpoints.
- Validate release commits before creating their version tags, then publish once from the validated
tag workflow.

## 0.5.0 — 2026-09-01

- Scope window chrome under `Window` (`Window.Widget`, `Window.MenuBar`, and `Window.StatusBar.*`) and remove the standalone pre-1.0 runtime exports.
Expand Down
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,21 @@ Both forms use the same build graph. CI checks that representative root imports

- Simple controls accept native props; compound controls use `Root` plus named parts and Base UI behavior. Use the [Base UI reference](https://base-ui.com/react/components) for exhaustive primitive props.
- Consumers provide labels and accessible names. Wrap overlay-heavy apps in `Layer.Provider`.
- `Window` adapts to its own inline size with `chrome="auto"`. Use `chrome="floating"` or
`chrome="stacked"` when geometry must remain fixed. The deprecated `responsive` adapter remains
available throughout 0.6.x and is scheduled for removal no earlier than 0.7.0.

## Common distinctions

- `Select` is fixed-list; `Combobox` searches listed values; `Autocomplete` keeps free-form text valid.
- Form popups match their anchor by default. Use `Select popupWidth="content"` or
`Combobox.Popup`/`Autocomplete.Popup width="content"` for longer lists; all three accept
`positionerProps`.
- `GroupBox` is visual grouping; `Fieldset` adds form semantics. Use `Fieldset.Root variant="plain"` with an accessible name when surrounding chrome supplies the visual boundary.
- `Button` emphasis (`variant="primary"`), default action (`defaultAction`), selection (`aria-pressed`), and keyboard focus are independent states.
- `Field.ActionRow` bottom-aligns labeled controls such as `Select` with adjacent buttons.
- `Field.ActionRow` bottom-aligns labeled controls such as `Select` with adjacent buttons. Its
`layout="auto"` default follows its own container; `"inline"` and `"stacked"` are explicit
overrides.

## Use locally

Expand All @@ -66,7 +74,19 @@ npm install /path/to/greyUI
- Feedback and content: Banner, Breadcrumbs, Empty, Loader, Pagination, Progress, Meter, SegmentedMeter, Toast, ScrollArea, Table, Badge, GroupBox, Separator
- Window chrome: Window (`Window.Widget`, `Window.MenuBar`, `Window.StatusBar.*`)

`Window` supports controlled/uncontrolled collapse and `responsive="stacked"` or `"floating"`. Use `Window.Content` for standard body rails and compose `Window.Header`, `Window.Description`, and `Window.Actions` for responsive in-body headers. `Popover.Popup.positionerProps` accepts Base UI positioning options such as virtual anchors.
`Window` supports controlled/uncontrolled collapse and container-aware `chrome="auto"` behavior;
use `"floating"` or `"stacked"` to override its chrome geometry. Use `Window.Content` for standard
body rails and compose `Window.Header`, `Window.Description`, and `Window.Actions` for in-body
headers. `Window.Header` also accepts `layout="auto" | "inline" | "stacked"`. The legacy
`responsive` prop remains as a deprecated adapter. `Popover.Popup.positionerProps` accepts Base UI
positioning options such as virtual anchors.

`Window.MenuBar` coordinates sibling `Menu.Root` components with menubar semantics, including arrow-key traversal and open-menu handoff. `Menu` includes item, link, checkbox, radio, group, and submenu primitives; `Menu.Popup.positionerProps` exposes Base UI positioning options for edge cases and nested menus.

For 0.5 migrations, replace `responsive="stacked"` with `chrome="auto"` and
`responsive="floating"` with `chrome="floating"`. If both props are present, `chrome` wins.
`Window.Header` and `Field.ActionRow` become container-aware by default without markup changes;
set their `layout` prop only when an explicit inline or stacked arrangement is required.

`Layer.Provider` routes overlays into stable top-level hosts; `Layer.Portal` exposes the same contract for custom content.

Expand Down
89 changes: 89 additions & 0 deletions docs/0.6.0-release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# greyUI 0.6.0 release plan

This document is the merge gate for the 0.6.0 release. Keep it in draft until every implementation
track has landed in the release pull request and its final public API is reflected in the docs.

## Documentation gate

- [ ] Replace 0.5 examples and prose that describe viewport breakpoints with the final
container-aware Window and field-layout API.
- [ ] Show automatic, inline, and stacked layout behavior using the exact prop names and defaults
exported by the final build.
- [ ] Document the coordinated `Window.MenuBar` keyboard model, menu handoff, submenu, checkbox,
radio, group, and link-item composition supported by 0.6.0.
- [ ] Document popup width policy and positioning overrides for Menu, Select, Combobox, and
Autocomplete, including the default for form controls.
- [ ] Keep permanent narrow-container examples for Window headers/actions and docs component grids;
confirm the examples do not depend on viewport width.
- [ ] Update README API conventions and component inventory after the public exports are final.
- [ ] Replace provisional CHANGELOG wording with the exact shipped behavior if implementation names
or compatibility decisions change during review.

## Migration notes

### Window chrome and content layout

- Replace `responsive="stacked"` with `chrome="auto"`. Both choose stacked chrome in narrow
containers, but `chrome="auto"` responds to the Window's own inline size instead of the viewport.
- Replace `responsive="floating"` with `chrome="floating"`.
- When both props are supplied, `chrome` takes precedence. The deprecated `responsive` adapter will
remain for the complete 0.6.x line and will be removed no earlier than 0.7.0.
- Existing `Window.Header` and `Field.ActionRow` markup needs no change. Both now default to
container-aware `layout="auto"`; use `layout="inline"` or `layout="stacked"` to force a layout.

### Menus

Keep each menu in its existing `Menu.Root`; coordinated behavior only requires those roots to be
siblings inside `Window.MenuBar`:

```tsx
<Window.MenuBar>
<Menu.Root>...</Menu.Root>
<Menu.Root>...</Menu.Root>
</Window.MenuBar>
```

This adds Left/Right Arrow traversal, open-menu handoff, disabled-trigger skipping, submenu keyboard
navigation, and Escape focus restoration. Standalone `Menu.Root` composition remains supported.

### Popup sizing and item parts

- Select, Combobox, and Autocomplete popups match their anchor width by default and clamp to Base
UI's available collision area. Use `popupWidth="content"` on Select or `width="content"` on
`Combobox.Popup` and `Autocomplete.Popup` to preserve content-sized lists. All expose
`positionerProps` for placement overrides.
- Combobox items should compose `Combobox.ItemText` and optional `Combobox.ItemIndicator`.
- Autocomplete items must compose `Autocomplete.ItemText`; add `Autocomplete.ItemIndicator` when
selection needs a visible marker. The implicit wrapper previously supplied around item children is
no longer injected.

## Release gate

- [ ] `package.json` and the root package in `package-lock.json` both report `0.6.0`.
- [ ] `CHANGELOG.md` has a dated 0.6.0 entry and an empty `Unreleased` heading.
- [ ] `npm run check` passes.
- [ ] `npm run build` passes.
- [ ] `npm run build:docs` and `npm run verify:docs` pass.
- [ ] `npm run pack:check` passes and the tarball contains the expected declarations, ESM, and CSS.
- [ ] Rendered docs have been reviewed at 1280, 768, 390, and 320 px, with narrow component
containers inside the 1280 px viewport.
- [ ] Window states, menubar keyboard behavior, and open overlays pass the planned regression matrix.
- [ ] A prerelease build has been exercised in the WorkbenchOS/bikeOS-style consumer examples.
- [ ] The release pull request contains no unrelated changes and all required checks are green.

## Tagging and publishing

Do not create `v0.6.0` manually. The release workflow owns the tag:

1. Merge the validated release pull request, including the `0.6.0` package version, to `main`.
2. The `main` release run resolves `v0.6.0`, installs dependencies, and runs the full package checks.
3. Only after those checks pass, the workflow creates and pushes `v0.6.0` at the merge commit.
4. The tag-triggered run verifies that the tag is on `main`, reruns the package checks, and publishes
`greyui@0.6.0` with npm Trusted Publishing provenance.
5. Deploy the docs with `npm run deploy:docs`, unless the connected Cloudflare project has already
deployed the `main` commit.
6. Verify the tag commit, npm version, provenance, and deployed docs before closing the release.

If validation fails before tag creation, fix the release pull request and merge the correction. If a
tag-triggered publish fails after the validated tag exists, rerun or repair that workflow; never move
or replace the published version tag.
4 changes: 2 additions & 2 deletions docs/src/dense-window-example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const gears = [

export function DenseWindowExample() {
return (
<Window title="Gearset" responsive="stacked" className="docs-dense-window">
<Window title="Gearset" chrome="auto" className="docs-dense-window">
<Window.Content>
<Window.Header>
<Window.Description>
Expand Down Expand Up @@ -87,7 +87,7 @@ export function DenseWindowExample() {
);
}

export const denseWindowCode = `<Window title="Gearset" responsive="stacked">
export const denseWindowCode = `<Window title="Gearset" chrome="auto">
<Window.Content>
<Window.Header>
<Window.Description>Configure the transmission.</Window.Description>
Expand Down
Loading
Loading