-
Notifications
You must be signed in to change notification settings - Fork 37
feat(breadcrumb): add breadcrumb #1147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5f95f9c
44b78a6
4440327
104d9ec
4a6538a
070ac7f
8b8c088
782b527
7ff7a96
27a0dc0
03ba9d4
fdad7fe
abf7293
ed1d53f
6144b0d
36d2c5c
a71571d
eb2445e
6507979
e085341
48687ca
4448abe
89ac7ce
dc2f739
7d64bac
44373f4
1db7a39
e3652ab
4bcce54
0d6d1c6
b182be1
07af1f0
6e6af98
814b856
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@tylertech/forge': minor | ||
| --- | ||
|
|
||
| feat(breadcrumb): added breadcrumb component |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| <section> | ||
| <h3 class="forge-typography--heading2">Default</h3> | ||
| <div> | ||
| <forge-breadcrumbs id="demo-breadcrumbs"></forge-breadcrumbs> | ||
| </div> | ||
| </section> | ||
|
|
||
| <section> | ||
| <h3 class="forge-typography--heading2">Constrained width (auto-collapse)</h3> | ||
| <div style="width: 300px; border: 1px dashed var(--forge-theme-outline); padding: 8px; resize: horizontal; overflow: hidden;"> | ||
| <forge-breadcrumbs id="demo-breadcrumbs-constrained"></forge-breadcrumbs> | ||
| </div> | ||
| </section> | ||
|
|
||
| <section> | ||
| <h3 class="forge-typography--heading2">Breadcrumb Items (slotted)</h3> | ||
| <div> | ||
| <forge-breadcrumbs id="demo-breadcrumbs-slot"> | ||
| <forge-breadcrumbs-item id="slot-item-0"></forge-breadcrumbs-item> | ||
| <forge-breadcrumbs-item id="slot-item-1"></forge-breadcrumbs-item> | ||
| <forge-breadcrumbs-item id="slot-item-2"></forge-breadcrumbs-item> | ||
| <forge-breadcrumbs-item id="slot-item-3"></forge-breadcrumbs-item> | ||
| </forge-breadcrumbs> | ||
| </div> | ||
| </section> | ||
|
|
||
| <section> | ||
| <h3 class="forge-typography--heading2">Custom separator icon (slotted)</h3> | ||
| <div> | ||
| <forge-breadcrumbs id="demo-breadcrumbs-custom-sep"> | ||
| <forge-icon slot="separator-icon" name="chevron_right"></forge-icon> | ||
| </forge-breadcrumbs> | ||
| </div> | ||
| </section> | ||
|
|
||
| <script type="module" src="breadcrumbs.ts"></script> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| <%- | ||
| include('./src/partials/page.ejs', { | ||
| page: { | ||
| title: 'Breadcrumbs', | ||
| includePath: './pages/breadcrumbs/breadcrumbs.ejs', | ||
| options: [ | ||
| { type: 'switch', label: 'Show home', id: 'opt-show-home' }, | ||
| { type: 'switch', label: 'Show icons', id: 'opt-icons' }, | ||
| { type: 'switch', label: 'Show secondary text', id: 'opt-secondary' }, | ||
| { type: 'switch', label: 'Show sibling routes', id: 'opt-siblings' }, | ||
| { | ||
| type: 'select', | ||
| label: 'Separator icon name', | ||
| id: 'opt-separator', | ||
| defaultValue: 'slash_forward', | ||
| options: [ | ||
| { label: 'Slash forward', value: 'slash_forward' }, | ||
| { label: 'Chevron right', value: 'chevron_right' }, | ||
| { label: 'Arrow right', value: 'arrow_right' } | ||
| ] | ||
| }, | ||
| { type: 'button', id: 'btn-add-crumb', label: 'Add crumb' }, | ||
| { type: 'button', id: 'btn-remove-crumb', label: 'Remove crumb' } | ||
| ] | ||
| } | ||
| }) | ||
| %> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| section { | ||
| margin-block-end: 24px; | ||
|
|
||
| h3 { | ||
| margin-block-end: 8px; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| import '$src/shared'; | ||
| import '@tylertech/forge/breadcrumbs'; | ||
| import type { BreadcrumbsComponent, BreadcrumbsItemComponent, ICrumbConfiguration } from '@tylertech/forge/breadcrumbs'; | ||
| import type { SwitchComponent } from '@tylertech/forge/switch'; | ||
| import type { SelectComponent } from '@tylertech/forge/select'; | ||
| import { IconRegistry } from '@tylertech/forge/icon'; | ||
| import { tylIconFolder, tylIconDescription, tylIconSettings, tylIconHome, tylIconChevronRight, tylIconArrowRight } from '@tylertech/tyler-icons'; | ||
|
|
||
| import './breadcrumbs.scss'; | ||
| import { ButtonComponent } from '@tylertech/forge/button'; | ||
|
|
||
| IconRegistry.define([tylIconFolder, tylIconDescription, tylIconSettings, tylIconHome, tylIconChevronRight, tylIconArrowRight]); | ||
|
|
||
| let showIcons = false; | ||
| let showSecondary = false; | ||
| let showSiblings = false; | ||
|
|
||
| const baseCrumbs: ICrumbConfiguration[] = [ | ||
| { label: 'Section 1', path: '/' }, | ||
| { label: 'Section 2', path: '/projects' }, | ||
| { label: 'Section 3', path: '/projects/forge' }, | ||
| { label: 'Current' } | ||
| ]; | ||
|
|
||
| const buildCrumbs = (): ICrumbConfiguration[] => | ||
| baseCrumbs.map((c, i) => ({ | ||
| ...c, | ||
| icon: showIcons && i < baseCrumbs.length - 1 ? ['home', 'folder', 'description', 'settings'][i] : undefined, | ||
| secondary: showSecondary && i < baseCrumbs.length - 1 ? `Level ${i + 1}` : undefined, | ||
| siblingRoutes: | ||
| showSiblings && i > 0 && i < baseCrumbs.length - 1 | ||
| ? [ | ||
| { label: `${c.label} Alt A`, path: `${c.path}-a` }, | ||
| { label: `${c.label} Alt B`, path: `${c.path}-b` } | ||
| ] | ||
| : undefined | ||
| })); | ||
|
|
||
| const breadcrumbs = document.querySelectorAll<BreadcrumbsComponent & HTMLElement>('#demo-breadcrumbs, #demo-breadcrumbs-constrained'); | ||
|
|
||
| const slotCrumbs: ICrumbConfiguration[] = [ | ||
| { label: 'Section 1', path: '/' }, | ||
| { label: 'Section 2', path: '/projects' }, | ||
| { label: 'Section 3', path: '/projects/forge' }, | ||
| { label: 'Current' } | ||
| ]; | ||
|
|
||
| const slotItems = Array.from(document.querySelectorAll<BreadcrumbsItemComponent>('#demo-breadcrumbs-slot forge-breadcrumbs-item')); | ||
| slotItems.forEach((item, i) => { | ||
| item.crumb = slotCrumbs[i]; | ||
| }); | ||
|
|
||
| const customSepBreadcrumbs = document.getElementById('demo-breadcrumbs-custom-sep') as BreadcrumbsComponent & HTMLElement; | ||
| customSepBreadcrumbs.crumbs = baseCrumbs; | ||
|
|
||
| const updateAll = (): void => { | ||
| const crumbs = buildCrumbs(); | ||
| breadcrumbs.forEach(bc => { | ||
| bc.crumbs = crumbs; | ||
| }); | ||
| }; | ||
|
|
||
| updateAll(); | ||
|
|
||
| const showHomeSwitch = document.getElementById('opt-show-home') as SwitchComponent; | ||
| const separatorSelect = document.getElementById('opt-separator') as SelectComponent; | ||
| const iconsSwitch = document.getElementById('opt-icons') as SwitchComponent; | ||
| const secondarySwitch = document.getElementById('opt-secondary') as SwitchComponent; | ||
| const siblingsSwitch = document.getElementById('opt-siblings') as SwitchComponent; | ||
| const addBtn = document.getElementById('btn-add-crumb') as ButtonComponent; | ||
| const removeBtn = document.getElementById('btn-remove-crumb') as ButtonComponent; | ||
|
|
||
| iconsSwitch.checked = showIcons; | ||
| secondarySwitch.checked = showSecondary; | ||
| siblingsSwitch.checked = showSiblings; | ||
|
|
||
| showHomeSwitch.addEventListener('forge-switch-change', () => { | ||
| breadcrumbs.forEach(bc => { | ||
| bc.showHome = showHomeSwitch.checked; | ||
| }); | ||
| }); | ||
|
|
||
| separatorSelect.addEventListener('change', () => { | ||
| breadcrumbs.forEach(bc => { | ||
| bc.separatorIconName = separatorSelect.value; | ||
| }); | ||
| }); | ||
|
|
||
| iconsSwitch.addEventListener('forge-switch-change', () => { | ||
| showIcons = iconsSwitch.checked; | ||
| updateAll(); | ||
| }); | ||
|
|
||
| secondarySwitch.addEventListener('forge-switch-change', () => { | ||
| showSecondary = secondarySwitch.checked; | ||
| updateAll(); | ||
| }); | ||
|
|
||
| siblingsSwitch.addEventListener('forge-switch-change', () => { | ||
| showSiblings = siblingsSwitch.checked; | ||
| updateAll(); | ||
| }); | ||
|
|
||
| addBtn.addEventListener('click', () => { | ||
| baseCrumbs.splice(baseCrumbs.length - 1, 0, { | ||
| label: `Section ${baseCrumbs.length}`, | ||
| path: `/section-${baseCrumbs.length}` | ||
| }); | ||
| updateAll(); | ||
| }); | ||
|
|
||
| removeBtn.addEventListener('click', () => { | ||
| if (baseCrumbs.length > 1) { | ||
| baseCrumbs.splice(baseCrumbs.length - 2, 1); | ||
| updateAll(); | ||
| } | ||
| }); | ||
|
|
||
| window.addEventListener('forge-breadcrumbs-crumb-select', evt => { | ||
| console.log('Crumb selected:', (evt as CustomEvent).detail); | ||
| }); | ||
|
|
||
| window.addEventListener('forge-breadcrumbs-home-click', () => { | ||
| console.log('Home clicked'); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| @use './token-utils' as *; | ||
| @use '../breadcrumbs/token-utils' as breadcrumb-tokens; | ||
| @use '../../icon' as icon; | ||
| @use '../../icon-button' as icon-button; | ||
| @use '../../core/styles/typography'; | ||
|
|
||
| @forward './token-utils'; | ||
|
|
||
| @mixin host { | ||
| display: flex; | ||
| align-items: flex-start; | ||
| white-space: nowrap; | ||
| } | ||
|
|
||
| @mixin base { | ||
| display: flex; | ||
| align-items: flex-start; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume we're using |
||
| } | ||
|
|
||
| @mixin link { | ||
| color: #{token(text-color)}; | ||
| cursor: pointer; | ||
| padding: var(--forge-spacing-xxsmall) var(--forge-spacing-xsmall); | ||
| border-radius: var(--forge-spacing-xxsmall); | ||
| font: inherit; | ||
| border: none; | ||
| background: none; | ||
| position: relative; | ||
| display: grid; | ||
| grid-template-columns: repeat(2, auto); | ||
| grid-template-rows: repeat(2, auto); | ||
| align-items: flex-start; | ||
| outline: none; | ||
| } | ||
|
|
||
| @mixin label-text { | ||
| @include typography.style(body2); | ||
| white-space: nowrap; | ||
| grid-column-start: 2; | ||
| grid-row-start: 1; | ||
| align-self: center; | ||
| } | ||
|
|
||
| @mixin secondary-text { | ||
| @include typography.style(label1); | ||
| color: #{token(secondary-text-color)}; | ||
| grid-column-start: 2; | ||
| grid-row-start: 2; | ||
| text-align: left; | ||
| } | ||
|
|
||
| @mixin crumb-icon { | ||
| color: #{token(icon-color)}; | ||
| margin-right: var(--forge-spacing-xsmall); | ||
| } | ||
|
|
||
| @mixin sibling-trigger { | ||
| @include icon-button.provide-theme( | ||
| ( | ||
| size: var(--forge-spacing-xlarge) | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| @mixin active { | ||
| padding: var(--forge-spacing-xxsmall) var(--forge-spacing-xsmall); | ||
| } | ||
|
|
||
| @mixin separator { | ||
| color: #{breadcrumb-tokens.token(separator-color)}; | ||
| padding-top: var(--forge-spacing-xxsmall); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would like to avoid this, see previous comment related to grid layout. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| @use '../../core/styles/tokens/breadcrumbs-item/tokens'; | ||
| @use '../../core/styles/tokens/token-utils'; | ||
|
|
||
| $_module: crumb; | ||
| $_tokens: tokens.$tokens; | ||
|
|
||
| @mixin provide-theme($theme) { | ||
| @include token-utils.provide-theme($_module, $_tokens, $theme); | ||
| } | ||
|
|
||
| @function token($name, $type: token) { | ||
| @return token-utils.token($_module, $_tokens, $name, $type); | ||
| } | ||
|
|
||
| @function declare($token) { | ||
| @return token-utils.declare($_module, $token); | ||
| } | ||
|
|
||
| @mixin override($token, $token-or-value, $type: token) { | ||
| @include token-utils.override($_module, $_tokens, $token, $token-or-value, $type); | ||
| } | ||
|
|
||
| @mixin tokens($includes: null, $excludes: null) { | ||
| @include token-utils.tokens($_module, $_tokens, $includes, $excludes); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| @use './core' as *; | ||
| @use '../../state-layer'; | ||
| @use '../../focus-indicator'; | ||
| @use '../../core/styles/shape'; | ||
|
|
||
| :host { | ||
| @include host; | ||
| } | ||
|
|
||
| :host([hidden]) { | ||
| display: none; | ||
| } | ||
|
|
||
| .forge-breadcrumbs-item { | ||
| @include tokens; | ||
| @include base; | ||
| } | ||
|
|
||
| .link { | ||
| @include link; | ||
|
|
||
| forge-state-layer { | ||
| @include state-layer.provide-theme( | ||
| ( | ||
| color: #{token(text-color)} | ||
| ) | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| forge-focus-indicator { | ||
| @include focus-indicator.provide-theme( | ||
|
Alex-Oxthorn marked this conversation as resolved.
|
||
| ( | ||
| color: #{token(text-color)}, | ||
| shape: #{shape.variable(medium)}, | ||
| outward-offset: 0px | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| .label-text { | ||
| @include label-text; | ||
| } | ||
|
|
||
| .secondary-text { | ||
| @include secondary-text; | ||
| } | ||
|
|
||
| .icon { | ||
| @include crumb-icon; | ||
| } | ||
|
|
||
| .sibling-trigger { | ||
| @include sibling-trigger; | ||
| } | ||
|
|
||
| .active { | ||
| @include active; | ||
| } | ||
|
|
||
| .separator { | ||
| @include separator; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the default separator icon font-size a little too large? Maybe we want |
||
| } | ||
|
|
||
| [hidden] { | ||
| display: none; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that
forge-breadcrumbs-crumb-selectis a standardEvent, there is nodetailproperty anymore...Also, this may present an issue if the event
targetis the<forge-breadcrumbs>element rather than the<forge-breadcrumb-item>element since we wouldn't be able to access which breadcrumb item was selected. I assume this is why theindexgetter was added? I'm not sure how I feel about that approach...We may want to keep this event as a
CustomEventand just include the crumb data in the detail? @samrichardsontylertech thoughts?