Add a set of breadcrumb DX components
Breadcrumb management would be really useful to have in this library.
It's a fairly common pattern for child interfaces to want to "push" a breadcrumb into a parent state without having implementation of where the actual breadcrumb is rendered higher in the component tree
Thinking about a potential public API for this, could be.
import { BreadcrumbRenderer, Breadcrumb } from 'ember-primatives';
export const ParentComponentSomewhere = <template>
<BreadcrumbRenderer class="flex items-center space-x-2 bg-gray-500 border rounded">
<:item as |BreadcrumbItem|>
<BreadcrumbItem class="inline-flex items-center hover:text-blue-200" />
</:item>
<:separator as |BreadcrumbSeparator|>
<BreadcrumbSeparator class="mx-4">
<ChevronIcon />
</BreadcrumbSeparator>
</:separator>
<BreadcrumbRenderer>
<Breadcrumb>
<a href="/">
<HomeIcon />
Home
</a>
</Breadcrumb>
<Breadcrumb>
<a href="/forum">Forum</a>
</Breadcrumb>
...
</template>
export const ChildComponentSomewhere = <template>
<Breadcrumb>
<a href="/forum/topics/{{@topic.id}}">{{@topic.name}}</a>
</Breadcrumb>
...
</template>
This would result in something to the effect of the following HTML being rendered in the DOM where the BreadcrumbRenderer was invoked:
<div class="flex items-center space-x-2 bg-gray-500 border rounded"> <!-- Classes applied to the `BreadcrumbRenderer` -->
<div class="inline-flex items-center hover:text-blue-200"> <!-- :item block is rendered for each <Breadcrumb> and contents yielded in `BreadcrumbItem` with applied classes -->
<a href="/"><HomeIcon /> Home</a>
</div>
<div class="mx-4"><ChevronIcon /></div> <!-- :separator block rendered between <Breadcrumb> contents -->
<div class="inline-flex items-center hover:text-blue-200"> <!-- :item block is rendered for each <Breadcrumb> and contents yielded in `BreadcrumbItem` with applied classes -->
<a href="/forum">Forum</a>
</div>
<div class="mx-4"><ChevronIcon /></div> <!-- :separator block rendered between <Breadcrumb> contents -->
<div class="inline-flex items-center hover:text-blue-200"> <!-- :item block is rendered for each <Breadcrumb> and contents yielded in `BreadcrumbItem` with applied classes -->
<a href="/forum/topics/{{@topic.id}}">{{@topic.name}}</a>
</div>
<div>
Considerations:
- Do we need a
:separatorblock?
- Css psuedo selectors can be used to add display only content between breadcrumbs, however this likely would be awkward and could limit teams on being able to render the content they want in a maintainable GTS format
- I potentially wouldn't want to limit what teams are ALLOWED to
-
ember-primitivesaims to be "bring your own CSS/HTML" and forcing teams into psuedo selectors will not be universally compatible -
:separatorcould be optional and when the block doesn't exist then nothing would be rendered between items, this could allow teams to still use psuedo selectors if they chose
- Does
:separatorneed to yield a component?
- Separator could be a simple block yield
- Yielding a
BlockSeparatorcomponent does allow some flexibility
- Are there any default
ariaroles/attrs that should be enforced? - Yields and blocks for
Breadcrumb?
- It could be useful for breadcrumbs to get yielded values:
isFirst or isLast: boolean(in case teams want to add something likearia-current="page"to the last breadcrumb [though this could be out of sync if more child UIs are rendered without adding their own breadcrumb]) - Should
Breadcrumbhave a way to modify its associated ~selector~ separator?
Thanks for the issue! I am hugely in favor of this!
Does :separator need to yield a component?
(need) only if there is an a11y benefit to doing so it may also be useful to have a wrapper element around this block for managing spacing or alignment consistency (flex/gap/basis/etc)
Are there any default aria roles/attrs that should be enforced?
I am not sure, would require research.
Some prior art though (and we may want to borrow some API design from here as well -- the simple case for the Svelte one is very nice):
- https://svelte-ux.techniq.dev/docs/components/Breadcrumb
- https://quasar.dev/vue-components/breadcrumbs#qbreadcrumbs-api
(in case teams want to add something like aria-current="page" to the last breadcrumb
this may be something we want to control, as one of the goals of ember-primitives is to encode many of the non-default a11y patterns
Should Breadcrumb have a way to modify its associated selector?
how do you mean?
Sorry meant to say
Should Breadcrumb have a way to modify its associated separator
Probably not necessary for first implementation/release, but I'm thinking ahead of if teams would want a way to modify the separator before/after a given breadcrumb. Giving a good DX and determining what "wins" could be real 🤮 though. And all that is for a "what if" (I don't personally have a use in our apps but this was more thinking for maximum customization)
if teams would want a way to modify the separator before/after a given breadcrumb
I would be surprised by this, as I've never seen per-item separator customization
Thinking about it having a "the instantiation modifies separator, this could be accomplished by omitting the :separator block and then using isLast/isFirst in the breadcrumb child population:
export const Separator = <template>
<div class="mx-4" ...attributes>
<ChevronIcon />
</div>
</template>
export const ParentComponentSomewhere = <template>
<BreadcrumbRenderer class="flex items-center space-x-2 bg-gray-500 border rounded">
<:item as |BreadcrumbItem|>
<BreadcrumbItem class="inline-flex items-center hover:text-blue-200" />
</:item>
<BreadcrumbRenderer>
<Breadcrumb as |bc|>
<a href="/">
<HomeIcon />
Home
</a>
{{#unless bc.isLast}}<Separator />{{/unless}}
</Breadcrumb>
<Breadcrumb>
<a href="/forum">Forum</a>
{{#unless bc.isLast}}<Separator class="some-customization-to-separator" />{{/unless}}
</Breadcrumb>
...
</template>
It's not perfectly equal to a per instance separator, since now the stuff you're using to separate each item is in the same element as the breadcrumb contents, but... I think it gives maximum flexibility without really weird edge cases
that's true -- might be worth adding to docs as an example, I suppose if someone really wanted to! :upside_down_face: :tada:
There's still room to go and the module scope TEMP_BREADCRUMB_MAP likely will break in all but the simplest app:
https://github.com/universal-ember/ember-primitives/compare/main...rtablada:ember-primitives:rt/poc-breadcrumb
There's some WIP and proof of concept stuff in there.
I mainly wanted to feel the basic handling and see far in-element would stretch. I'm using some of the same patterns (and a lot of doc copy 🍝 ) from portals to try and target elements elsewhere in the DOM.