Skip to content

Extract project components; add ConfirmDialog - #149

Merged
microbit-matt-hillsdon merged 7 commits into
mainfrom
project-components
Sep 14, 2026
Merged

microbit-matt-hillsdon merged 7 commits into
mainfrom
project-components

Conversation

@microbit-matt-hillsdon

@microbit-matt-hillsdon microbit-matt-hillsdon commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

No description provided.

The pieces of a "your projects" page that ml-trainer and the Python
Editor prototype had each written, lifted from ml-trainer with the
app-specific parts turned into props:

- ProjectCard: actions row (optional selection checkbox, more menu with
  open, rename, duplicate and delete, hidden skip-to-toolbar link for
  keyboard users), the app's own content in the middle, the name as the
  card's link and when it was last modified. Apps pass a ProjectSummary
  (id, name, timestamp) or a superset.
- ProjectsToolbar, SearchInput, SortInput: the controls above the grid.
- NameProjectDialog and ConfirmDialog.
- useProjectActions: the rename, duplicate and delete flows behind the
  card menus and the toolbar. Renders the two dialogs, tracks which
  project is being acted on and returns focus to the menu button that
  opened a dialog. The app supplies what each action does and logs what
  it wants.
- rankProjects, sortProjects and useProjectSelection for the list state,
  with the app supplying secondary search terms (file names, action
  names). formatTimeAgo for the card's relative time.

Page layout, the grid, the home page carousel and where the toolbar sits
at each width stay app-side; the "Projects page example" story shows one
composition. Short-viewport tweaks that used ml-trainer's own Panda
condition go through the card's bodyCss prop rather than into the
shared code.
A confirm dialog is a standard overlay, not a project thing: ml-trainer
uses it for settings and data samples as well, and the Python Editor has
its own. It now sits beside Modal, with the Cancel string, and shows
under Overlays in Storybook. ui-patterns uses it from there.

The peer ranges of ui-patterns and ui-carousel move to the next
@microbit/ui minor, which this needs.
- Separate onRename and onDuplicate callbacks on ProjectCard and
  ProjectsToolbar, and rename/duplicate on the hook, instead of one
  callback with a "rename" | "duplicate" discriminator that leaked the
  shared dialog's implementation into every caller.
- The card passes its menu button to the action callbacks instead of
  exposing a setFinalFocusRef prop; the hook hands it to the dialog to
  return focus to, since the menu has closed by then.
- The card's relative time is a <time> element with the ISO datetime and
  the full date as its title, so hover and assistive tech can get the
  exact date.
- SearchInput is type="search" for the searchbox role, with WebKit's
  native clear button hidden in favour of ours.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 11, 2026

Copy link
Copy Markdown

Deploying ui with  Cloudflare Pages  Cloudflare Pages

Latest commit: 67c491c
Status: ✅  Deploy successful!
Preview URL: https://994a6fab.ui-2wg.pages.dev
Branch Preview URL: https://project-components.ui-2wg.pages.dev

View logs

},
"peerDependencies": {
"@microbit/ui": "^0.4.0",
"@microbit/ui": "^0.5.0",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit weird but OK, we'll have to release ui-v0.5.0 first then ui-patterns-0.7.0.

Comment thread packages/ui-patterns/README.md Outdated
* A rounded search box with a leading icon and a clear button while there
* is text. Clearing returns focus to the box.
*/
export const SearchInput = ({

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted faithfully, but I think it might be worth a discussion about this rounding. Do we really want it here?
Classroom has this rounding... but the base UI components do not and they don't when used in other locations (dialogs, edit project name in ml-trainer).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Experiment here:

#153

@microbit-robert WDYT?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decoupling this from this PR, so for now we just extract what we have in ml-trainer.

- Toolbar delete with a single selection resolves to that project, so
  the confirmation names it rather than saying "1 projects"; the
  multi-delete text gains an ICU plural.
- NameProjectDialog shows the empty-name error only once edited, and
  selects the name on the first focus after opening rather than every
  focus.
- Rename SearchInput/SortInput/SortDirection to Project*; the hook's
  requestDelete to delete; ProjectCard's onSelected to onToggleSelected.
- useProjectActions takes selectedIds as an array, not a getter.
- ProjectsToolbar forwards a handle whose focus() lands on its first
  button, so onSkipToToolbar needs no DOM query; the page example wires
  it.
- Logical properties for the card action radii and margin, the name's
  text alignment and the toolbar divider.
- sortProjects takes an optional locale, collating with Intl.Collator.
The "select the name on first focus" flag was a ref reset during the
render-time adjustment that runs when the dialog opens, which
react-hooks/refs rejects. Make it state instead, so it resets alongside
the name and edited flags.
"description": "Heading of the dialog confirming deletion of several projects"
},
"ui-patterns.delete-projects-confirm-text": {
"defaultMessage": "Are you sure you want to delete {numProjects, plural, one {# project} other {# projects}}?",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this one string to use ICU plurals, will have to deal with some translation fallout.

The reducer kept ids of deleted projects in its raw state. selectedIds
filtered them against the live list, but lastSelectedIds (used by the
sliding-out toolbar) did not, so deleting two projects then selecting a
third showed "Delete 3 projects" in the bottom toolbar while the top
count was correct.

The toggle now works from the live selection. Pruning happens there
rather than when the list changes so the toolbar keeps its count while
it slides out after a delete.
@microbit-matt-hillsdon
microbit-matt-hillsdon merged commit af7042c into main Sep 14, 2026
4 checks passed
@microbit-matt-hillsdon
microbit-matt-hillsdon deleted the project-components branch September 14, 2026 12:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant