Skip to content

KToolbar: slim public interface, drop Keen dep, refresh examples#1236

Open
nucleogenesis wants to merge 14 commits into
learningequality:developfrom
nucleogenesis:1183--ktoolbar-cleanup
Open

KToolbar: slim public interface, drop Keen dep, refresh examples#1236
nucleogenesis wants to merge 14 commits into
learningequality:developfrom
nucleogenesis:1183--ktoolbar-cleanup

Conversation

@nucleogenesis

@nucleogenesis nucleogenesis commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Description

Slims KToolbar's public interface, drops the last direct Keen dependency (UiProgressLinear), renames the icon/actions slots to #leading-actions/#trailing-actions, bakes title truncation into the default title slot, adds a Vuetify-style #extension slot for sub-toolbar content, and refreshes the example set with semantic names aligned to the new interface. Implements the PR #1153 cleanup with the breaking changes that PR deferred, plus the brand prop removal, slot rename, built-in truncation, and #extension slot addition requested in this round of review (Alex r3130185513 / r3130177502 / r3130658714 / r3130750618).

Issue addressed

Addresses #1183

Before/after screenshots

/ktoolbar docs page on the Netlify deploy preview — covers all seven examples (Default, Brand color, Inverted palette, Navigation and actions, Brand, Not raised, Extension) plus the updated Props and Slots tables (Props now lists only title and raised; Slots lists leading-actions, brand, default, navigation, trailing-actions, extension):

/ktoolbar docs page after this PR

Title truncation demo — captured from the local playground at five widths to verify behavior across the truncation envelope (short title untouched, long title in wide containers, long title in narrow containers, narrow + brand-color combination):

Title truncation behavior across widths

Changelog

  • Description: Slims KToolbar's public interface and drops its last direct Keen dependency. Removes the props type, textColor, navIcon, removeNavIcon, progressPosition, loading, and brand; removes the nav-icon-click event; renames the #icon slot to #leading-actions and the #actions slot to #trailing-actions; removes the default #icon slot fallback. Renames the internal .k-toolbar-nav-icon CSS class to .k-toolbar-leading-actions to match the slot. Deletes the now-unused lib/keen/UiProgressLinear.vue. Default bg/text now come from $themeTokens.surface / $themeTokens.text and are overridable via native :style. raised is the only styling prop that remains.

  • Products impact: removed API

  • Addresses: Complete KToolbar public interface, make it fully themeable, and cleanup obsolete Keen code #1183

  • Components: KToolbar

  • Breaking: yes

  • Impacts a11y: no

  • Guidance: Migrate per the mapping below:

    • type="default" → remove (now implicit)
    • type="colored":style="{ backgroundColor: <theme token> }"
    • type="clear" → remove (use :style="{ backgroundColor: 'transparent' }" if needed; :raised="false" drops the shadow)
    • textColor="black|white|…":style="{ color: <value> }"
    • navIcon="…" / removeNavIcon → pass your own KIconButton (or similar) inside the #leading-actions slot; omit slot content to hide the leading-actions area entirely
    • @nav-icon-click → attach @click to the element you put inside the #leading-actions slot
    • progressPosition / loading → use a separate KLinearLoader above or below the toolbar
    • brand="…" prop → <template #brand>…</template> slot
    • <template #icon>…</template><template #leading-actions>…</template>
    • <template #actions>…</template><template #trailing-actions>…</template>
    • Any consumer CSS targeting /deep/ .k-toolbar-nav-icon → update the selector to .k-toolbar-leading-actions (Kolibri's ImmersiveToolbar.vue and LearningActivityBar.vue both do this and need coordinated updates in the companion branch)
    • The bottom border that used to appear with type="default" :raised="false" is gone; nothing in the Kolibri codebase depended on it. If you need it, add it yourself via :style.
    • lib/keen/UiProgressLinear.vue is removed; no LE consumer imports it directly, but any downstream code that does will need to drop the reference.
  • Description: The default title slot now wraps the title prop in KTextTruncator, so long titles ellipsis-truncate automatically when space runs short. .k-toolbar-left and .k-toolbar-title got min-width: 0 to let the flex chain shrink, and .k-toolbar-title is line-height: 1 to keep the truncated text vertically centered with the leading/trailing icons.

  • Products impact: behavior change

  • Addresses: Complete KToolbar public interface, make it fully themeable, and cleanup obsolete Keen code #1183

  • Components: KToolbar

  • Breaking: no

  • Impacts a11y: no

  • Guidance: Consumers passing title="…" see the same visible text and gain ellipsis-on-overflow for free. Consumers overriding the default slot are unaffected. Anyone using /deep/ .k-toolbar-title { … } workarounds for title truncation can drop those overrides.

  • Description: Adds a Vuetify-style #extension slot to KToolbar that renders below the main row but inside the same visual surface (shares background and shadow). Intended for tabs, sub-navigation, and breadcrumbs that conceptually belong to the toolbar rather than the page below it. Internally restructures the template so the existing flex row lives inside a new .k-toolbar-main div; the outer .k-toolbar keeps only surface-level rules (position, font, is-raised shadow). The .k-toolbar-extension defaults to display: flex; align-items: center; min-height: 3rem; padding: 0 16px (matching Vuetify's 48px extension default and mirroring the main row's horizontal padding).

  • Products impact: new API

  • Addresses: Complete KToolbar public interface, make it fully themeable, and cleanup obsolete Keen code #1183

  • Components: KToolbar

  • Breaking: no

  • Impacts a11y: no

  • Guidance: Purely additive — existing consumers without an #extension slot render visually identically. Consumers (notably Kolibri's AppBar) that currently wrap KToolbar in a parent div to provide a colored sub-nav strip below can move that strip into the #extension slot, set the toolbar's :style color directly, and drop the wrapper div. Edge-to-edge content (e.g. tab strips with bottom borders) can use negative horizontal margins on the slot content to override the default 16px padding.

  • Description: Rewrites the KToolbar documentation page and example set. Old "Light theme" / "Dark theme" examples replaced by seven semantic examples (Default, AppBarColor, Inverted, NavigationAndActions, Brand, NotRaised, Extension). The "Overriding colors" docs section is split into a "Brand color" demo (most common Kolibri pattern, $themeTokens.appBar) and an "Inverted palette" demo (immersive bars, $themeTokens.appBarDark). Adds a Percy visual test harness covering all examples.

  • Products impact: documentation

  • Addresses: Complete KToolbar public interface, make it fully themeable, and cleanup obsolete Keen code #1183

  • Components: KToolbar

  • Breaking: no

  • Impacts a11y: no

  • Guidance: -

Steps to test

  1. Preview the Netlify deploy for this branch; visit the /ktoolbar page.
  2. Verify the seven examples render: Default, Brand color (uses $themeTokens.appBar), Inverted palette, Navigation and actions, Brand, Not raised, Extension (sub-toolbar).
  3. Resize the browser narrow enough for the title to ellipsis-truncate; confirm the truncated text stays vertically centered with the leading/trailing icons.
  4. Confirm the Extension example renders the sub-nav row directly below the main toolbar row inside the same yellow surface (shared background + shadow).
  5. Check the Percy build for the new KToolbar visual snapshot.
  6. Confirm the changelog entries read cleanly in the PR description (they'll auto-append to CHANGELOG.md on merge).

Testing checklist

  • Contributor has fully tested the PR manually
  • Before/after screenshots added (will attach after Netlify preview deploys)
  • New KToolbar Percy visual test harness covers the example states
  • The change is described in the changelog section above
  • docs/pages/ktoolbar.vue updated to wire the new example filenames and drop "Light theme" / "Dark theme" language

Reviewer guidance

Specific areas worth a second look:

  • Default colors + :style override behaviorlib/KToolbar/index.vue:78-79. toolbarStyles returns $themeTokens.surface / $themeTokens.text. The override path relies on Vue 2 merging a consumer's :style onto the root element with consumer values winning on key conflicts. Worth a visual confirmation on the deploy preview: the Inverted and Brand color examples should fully replace the defaults, not blend with them.

  • Slot rename and CSS class follow-throughlib/KToolbar/index.vue:14, 46, 115. Slots #icon/#actions renamed to #leading-actions/#trailing-actions (Alex r3130177502). Internal CSS class .k-toolbar-nav-icon renamed to .k-toolbar-leading-actions to keep slot/class naming consistent. The Kolibri companion branch needs paired updates because it uses /deep/ selectors against the old class name in ImmersiveToolbar.vue and LearningActivityBar.vue.

  • brand prop removallib/KToolbar/index.vue:17-23. Verified no LE consumer passes brand= (Kolibri companion grep). The slot was always the right surface for this; the prop fallback was vestigial.

  • #leading-actions slot visibility changelib/KToolbar/index.vue:9-15. Now driven entirely by slot presence (v-if="$slots['leading-actions']"); the default UiIconButton fallback is gone. Audit across LE showed no consumer relied on the fallback, but flag anything we missed.

  • Built-in title truncation + line-height: 1lib/KToolbar/index.vue:31, 111, 135-136. Default title slot wraps title in KTextTruncator (Alex r3130658714). .k-toolbar-left and .k-toolbar-title get min-width: 0 so the flex chain can shrink; .k-toolbar-title is line-height: 1 to compensate for KTextTruncator's overflow: hidden shifting its inline-block baseline to the bottom margin edge (CSS spec behavior). Without the line-height tweak the title sits visually higher than the icon flex siblings. Verified across short / long-wide / long-narrow / brand-color cases on the playground.

  • #extension slot + template restructurelib/KToolbar/index.vue:8, 51-57, 101-126. The existing flex row is now wrapped in a new .k-toolbar-main div; the outer .k-toolbar keeps only surface-level rules (position, font, is-raised shadow). .k-toolbar-extension is rendered as a sibling below when the slot has content. Visually identical for consumers that don't use #extension (the extra wrapper div is layout-neutral). Worth eyeballing the deploy preview's Extension example to confirm the sub-nav row sits flush below the main row inside the same surface (shared background + shadow), with no visible seam between them.

  • Removed "flat with bottom border" combinationlib/KToolbar/index.vue scss. raised=false now emits no shadow AND no border (previously type="default" + raised=false produced a bottom border). Kolibri's AppBar pairs raised=false with type="clear" specifically to avoid that border, so it's safe there — but the companion Kolibri PR will need to visually confirm.

  • Commit authorship — one commit (Add KToolbar examples for brand, navigation/actions, and not-raised) is authored by MisRob since those three example files port verbatim from PR Make KToolbar themeable, fix textColor validator check error in Kolibri, and remove obsolete Keen code #1153. Everything else is new work here.

Comments

A Kolibri companion branch (kds-ktoolbar-companion-fixes) is waiting in the adjacent worktree for the downstream migrations enumerated in the Guidance section. The migration scope grew during this review round — it now also covers the slot rename (#icon / #actions#leading-actions / #trailing-actions) and the matching /deep/ .k-toolbar-nav-icon selector updates in ImmersiveToolbar.vue and LearningActivityBar.vue, in addition to the original consumer migrations (AppBar, ImmersiveToolbar, LearningActivityBar, ContentModal). Will open as a linked PR once this one is ready for review.

#extension slot — Kolibri-side migration scope

A subagent audit of the Kolibri companion worktree turned up:

  • AppBar.vue is the only consumer with the wrapper-div + transparent-KToolbar + sub-nav-below pattern. ImmersiveToolbar, LearningActivityBar, and ContentModal are standalone and don't have sub-nav stacked below.
  • Main complication on the Kolibri side: AppBar.vue dual-renders its Navbar — once inside KToolbar's #navigation slot (when it fits) and once in a sibling subpage-nav div below (when it overflows). The @update-overflow-count event from Navbar drives the responsive switch. Migrating to #extension can't preserve the dual-render shape as-is; the overflow logic needs a redesign in the Kolibri PR (likely: render unconditionally in #extension and use CSS media queries to swap visibility, or push overflow detection into KToolbar itself). Doesn't block this PR — the slot is purely additive — but flagging it as the work that follows.
  • Smaller items for the Kolibri PR: AppBarPage's appBarHeight DOM measurement still works after the migration; ScrollingHeader's hard-coded translateY(-200px) could break if extension content makes the toolbar tall (already commented in-source as "200px is arbitrary"); /deep/ .sub-nav .items margin reset will need to relocate into the Navbar component or use a slot-scoped selector.

AI usage

Used Claude Code to aggregate relevant comments and gather understanding, draft and review commits, and compose this PR body. Verified changes locally throughout, including a playground-driven visual sweep of the title-truncation alignment fix.

@AlexVelezLl AlexVelezLl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks really solid! I've left some comments/questions, but nothing blocking!

Comment thread lib/KToolbar/index.vue Outdated
/>
</slot>
<!-- @slot Navigation icon area (typically a KIconButton). No content renders if the slot is empty. -->
<slot name="icon" />

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Now that we are here, could we replace the name of this prop? Perhaps we can pair this change with the actions slot too, and have something like left-actions/right-actions or leading-actions/trailing-actions? (I think I prefer the latter)

Comment thread lib/KToolbar/index.vue Outdated
Comment on lines 73 to 76
@@ -119,89 +74,31 @@
type: String,
default: '',
},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we may also want to remove this prop? I don't see it being used anywhere. And we can just use the brand slot

Comment thread lib/KToolbar/index.vue

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It'd be great if we could have the toolbar title already with truncation by default. We could change the title slot to be

      <slot>
        <div
          v-if="title"
          class="k-toolbar-title"
        >
          <KTextTruncator
            v-if="title"
            :text="title"
          />
        </div>
      </slot

And we would need to set a min-width: 0 to the .k-toolbar-left class, and change .k-toolbar-title to only contain min-width: 0 (and no other style would be needed).

This would fix the need for a couple of PRs that have been around title truncation on KToolbar, where we have had to use deep styles to fix it.

Comment thread lib/KToolbar/index.vue

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I wonder if an #extension slot would be useful for Kolibri and Studio? Just like vuetify. Both Kolibri and Studio use this concept of "extension", which is just another div below the navbar to show subnav elements. Currently, in Kolibri, we set the toolbar background to transparent, and a wrapper div is the one in charge of setting the color and background, but it would make sense to me if it were part of the toolbar component itself. And I don't think it would be too difficult, as it is just one slot. What do you think? @nucleogenesis @MisRob

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Currently, in Kolibri, we set the toolbar background to transparent, and a wrapper div is the one in charge of setting the color and background, but it would make sense to me if it were part of the toolbar component itself.

I think I don't follow, is there an example I could look at? The main purpose of the extension slot has something to do with colors, or is it more general space for placing sub-toolbar under the main toolbar?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah, apologies @MisRob 😅. Yes, this happens, for example, with the AppBar component in Kolibri, where the main Kolibri toolbar (the entire yellow rectangle) isn't really rendered by KToolbar, but it is a wrapper div that sets the background color and shadows:

image

While the KToolbar component is just this div:
image

Appbar sets the toolbar to be transparent and without shadows, so that the parent div that also embraces that other div below the KToolbar component can have the same background. Code here. But IMO, the entire yellow rectangle is the toolbar, and KToolbar should also support this layout instead of doing these workarounds, just like Vuetify does, for example.

Benefits of doing this may include ensuring consistency across toolbars, better maintainability, because if we ever decide to change the shadows attributes for toolbars, we would only need to do it on KToolbar, and wouldn't have to recall that the main Kolibri toolbar isn't using the KToolbar shadow specs. And now we also need to do the same on Studio. And in general, having a better concept of "toolbar".

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah okay, thank you. Yes your suggestion makes much sense :)

We will just need to be careful around not enforcing <nav> markup, because e.g. in the case of main navigation, there will be two <nav>, each aria-labelled differently. So KToolbar would ideally not have opinions and let <nav>(s) be provided via slots. But I think that would need to apply in any case, due to many contexts we use KToolbar for?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, I agree, navs should be provided via slot. I think nav enforcement is perhaps the difference between a toolbar and an appbar?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think so, yes :)

Comment on lines +2 to +3
T6 (AC16). Visual test harness for KToolbar covering the five examples
landed in T4. Structure follows PR #1153's KToolbarVisualTest.vue, with

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not pretty sure what is T6, AC16 and T4 😅 . Do we need this comment?

Comment on lines +1 to +7
<!--
Ported WHOLESALE from PR #1153 (examples/KToolbar/NavigationAndActions.vue
in that PR's diff). No changes — this example only exercises the
`#navigation` and `#actions` slots, both of which are retained in the
slimmed-down public interface. Demonstrates AC14: the slots are still the
primary extension point now that props for layout/styling are gone.
-->

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are these comments needed?

Comment thread examples/KToolbar/LightTheme.vue Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think it'd be great if we keep an example of using the appBar color, as it's the most common example used across the ecosystem.

nucleogenesis added a commit to nucleogenesis/kolibri-design-system-1 that referenced this pull request Apr 27, 2026
Per PR learningequality#1236 review (AlexVelezLl r3130768684): preserve an appBar color
example since it's the most common toolbar color across the Kolibri
ecosystem. Splits the docs page "Overriding colors" section into two
h3s — "Brand color" (AppBarColor.vue, $themeTokens.appBar) first since
it's the common case, and "Inverted palette" (Inverted.vue,
$themeTokens.appBarDark) for fullscreen modals / immersive bars.

Renames the Inverted visual-test entry from "custom colors via :style"
to "appBarDark via :style" so the two color-override rows name the
token they demonstrate symmetrically.
nucleogenesis added a commit to nucleogenesis/kolibri-design-system-1 that referenced this pull request Apr 27, 2026
…ctions

Per PR learningequality#1236 review (AlexVelezLl r3130177502): pair the rename of the
#icon slot with the #actions slot for symmetric leading/trailing-actions
naming. Breaking change.

  - #icon → #leading-actions
  - #actions → #trailing-actions
  - .k-toolbar-nav-icon CSS class → .k-toolbar-leading-actions
    (matches Kolibri consumers' /deep/ selectors which need an
    accompanying migration anyway)

#navigation and #brand slots are unchanged. Examples (Inverted,
AppBarColor, NavigationAndActions) updated to the new slot names.
nucleogenesis added a commit to nucleogenesis/kolibri-design-system-1 that referenced this pull request Apr 27, 2026
Per PR learningequality#1236 review (AlexVelezLl r3130658714): wrap the default title
slot content in KTextTruncator so consumers no longer need /deep/
.k-toolbar-title workarounds for truncating long titles. Follows Alex's
suggested implementation.

  - Default title slot content uses KTextTruncator (globally registered
    via KThemePlugin, no import needed).
  - .k-toolbar-left gets min-width: 0 so the left flex container can
    shrink below its intrinsic content size, enabling the title to
    truncate when space runs short.
  - .k-toolbar-title swaps overflow: hidden / white-space: nowrap (no
    longer needed — KTextTruncator handles truncation itself) for
    min-width: 0 plus line-height: 1.

The line-height: 1 is needed because KTextTruncator's truncate class
applies overflow: hidden to its inline-block span, which by CSS spec
shifts that inline-block's baseline from the text baseline to the bottom
margin edge. Inside .k-toolbar-title the resulting baseline-shifted
inline-block ends up visually offset from the icon flex siblings;
collapsing the line-box leading to 1em removes the gap that the offset
lives in. Verified visually across short, long-wide, long-narrow, and
brand-color toolbars in the playground; descenders fit comfortably
within the 56px-tall .k-toolbar.

Not a breaking change for consumers passing title="..." (same visible
text, gains ellipsis on overflow). Consumers overriding the default slot
are unaffected.
@nucleogenesis nucleogenesis force-pushed the 1183--ktoolbar-cleanup branch from afa62da to 66f14c0 Compare April 27, 2026 21:36
nucleogenesis and others added 11 commits April 27, 2026 15:26
Relocate lib/KToolbar.vue to lib/KToolbar/index.vue ahead of cleanup work
in the component. No behavior change.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
KToolbar was the only consumer and no longer renders a progress bar
(the `loading` + `progressPosition` props were removed). Nothing else
imports this file.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ported from PR learningequality#1153 (commit 2188ef5). Exercises #brand, #navigation,
#actions slots and the `raised` prop — all retained in the slimmed-down
KToolbar public interface.
Replaces the old LightTheme.vue / DarkTheme.vue with two semantic examples:
- Default.vue: bare toolbar using the component's default theme-token
  colors ($themeTokens.surface / $themeTokens.text)
- Inverted.vue: canonical demo of overriding the defaults via native
  :style, replacing the removed textColor/backgroundColor props (full-
  screen modal / immersive bar palette)

Also adds inline review comments to the three MisRob-authored examples
ported in the previous commit, tying each to the PR learningequality#1153 discussions
that motivated its inclusion. The theme-language rename follows the
feedback in PR learningequality#1153 r2600351212 / r2627216880.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a Percy visual test harness covering the five KToolbar examples:
Default, Inverted, NotRaised, NavigationAndActions, Brand.

- lib/KToolbar/__tests__/visual.spec.js — boilerplate matching other
  components (KLogo, KBreadcrumbs, etc.)
- lib/KToolbar/__tests__/components/KToolbarVisualTest.vue — harness
  structure ported from PR learningequality#1153 with the example list updated to match
  the slimmer set landed in T4 (5 examples vs. her 7).
- jest.conf/visual.load-test-components.js — register the new harness
  alongside KBreadcrumbs / KButton.

Co-Authored-By: MisRob <robosovam@gmail.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Updates the docs page to reference the five new example filenames
(Default, Inverted, NavigationAndActions, Brand, NotRaised) and drops
the "Light theme" / "Dark theme" section headings flagged as misleading
in PR learningequality#1153.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per PR learningequality#1236 review (AlexVelezLl r3130768684): preserve an appBar color
example since it's the most common toolbar color across the Kolibri
ecosystem. Splits the docs page "Overriding colors" section into two
h3s — "Brand color" (AppBarColor.vue, $themeTokens.appBar) first since
it's the common case, and "Inverted palette" (Inverted.vue,
$themeTokens.appBarDark) for fullscreen modals / immersive bars.

Renames the Inverted visual-test entry from "custom colors via :style"
to "appBarDark via :style" so the two color-override rows name the
token they demonstrate symmetrically.
…ctions

Per PR learningequality#1236 review (AlexVelezLl r3130177502): pair the rename of the
#icon slot with the #actions slot for symmetric leading/trailing-actions
naming. Breaking change.

  - #icon → #leading-actions
  - #actions → #trailing-actions
  - .k-toolbar-nav-icon CSS class → .k-toolbar-leading-actions
    (matches Kolibri consumers' /deep/ selectors which need an
    accompanying migration anyway)

#navigation and #brand slots are unchanged. Examples (Inverted,
AppBarColor, NavigationAndActions) updated to the new slot names.
Per PR learningequality#1236 review (AlexVelezLl r3130658714): wrap the default title
slot content in KTextTruncator so consumers no longer need /deep/
.k-toolbar-title workarounds for truncating long titles. Follows Alex's
suggested implementation.

  - Default title slot content uses KTextTruncator (globally registered
    via KThemePlugin, no import needed).
  - .k-toolbar-left gets min-width: 0 so the left flex container can
    shrink below its intrinsic content size, enabling the title to
    truncate when space runs short.
  - .k-toolbar-title swaps overflow: hidden / white-space: nowrap (no
    longer needed — KTextTruncator handles truncation itself) for
    min-width: 0 plus line-height: 1.

The line-height: 1 is needed because KTextTruncator's truncate class
applies overflow: hidden to its inline-block span, which by CSS spec
shifts that inline-block's baseline from the text baseline to the bottom
margin edge. Inside .k-toolbar-title the resulting baseline-shifted
inline-block ends up visually offset from the icon flex siblings;
collapsing the line-box leading to 1em removes the gap that the offset
lives in. Verified visually across short, long-wide, long-narrow, and
brand-color toolbars in the playground; descenders fit comfortably
within the 56px-tall .k-toolbar.

Not a breaking change for consumers passing title="..." (same visible
text, gains ellipsis on overflow). Consumers overriding the default slot
are unaffected.
@nucleogenesis nucleogenesis force-pushed the 1183--ktoolbar-cleanup branch from 66f14c0 to a0c8f7c Compare April 27, 2026 22:50
nucleogenesis added a commit to nucleogenesis/kolibri-design-system-1 that referenced this pull request Apr 28, 2026
Per PR learningequality#1236 review (AlexVelezLl r3130750618): add a Vuetify-style
#extension slot that renders below the main toolbar row but inside
the same visual surface (shares background and shadow). Intended for
tabs, sub-navigation, and breadcrumbs that conceptually belong to
the toolbar rather than the page below it.

Structural change: the existing flex row that was `.k-toolbar` is now
wrapped in a `.k-toolbar-main` div, with `.k-toolbar-extension`
rendered as a sibling below when the slot has content. The outer
`.k-toolbar` keeps only the surface-level rules (position, font,
is-raised shadow); row layout (flex, height, padding) moved onto
`.k-toolbar-main`. Existing consumers without an `#extension` slot
render visually identically — purely additive, not a breaking change.

`.k-toolbar-extension` defaults to flex + align-items: center +
min-height: 3rem (matching Vuetify's 48px default) + padding: 0 16px
to mirror the main row's horizontal padding. Consumers wanting
edge-to-edge content (e.g. tab strips) can use negative margins on
their slot content.

Adds an Extension.vue example demoing the typical Kolibri AppBar
layout (appBar background + leading menu icon + sub-nav router
links in the extension), registers it in the visual test harness
and the docs page.
Per PR learningequality#1236 review (AlexVelezLl r3130750618): add a Vuetify-style
#extension slot that renders below the main toolbar row but inside
the same visual surface (shares background and shadow). Intended for
tabs, sub-navigation, and breadcrumbs that conceptually belong to
the toolbar rather than the page below it.

Structural change: the existing flex row that was `.k-toolbar` is now
wrapped in a `.k-toolbar-main` div, with `.k-toolbar-extension`
rendered as a sibling below when the slot has content. The outer
`.k-toolbar` keeps only the surface-level rules (position, font,
is-raised shadow); row layout (flex, height, padding) moved onto
`.k-toolbar-main`. Existing consumers without an `#extension` slot
render visually identically — purely additive, not a breaking change.

`.k-toolbar-extension` defaults to flex + align-items: center +
min-height: 3rem (matching Vuetify's 48px default) + padding: 0 16px
to mirror the main row's horizontal padding. Consumers wanting
edge-to-edge content (e.g. tab strips) can use negative margins on
their slot content.

Adds an Extension.vue example demoing the typical Kolibri AppBar
layout (appBar background + leading menu icon + sub-nav router
links in the extension), registers it in the visual test harness
and the docs page.
@nucleogenesis

nucleogenesis commented Apr 28, 2026

Copy link
Copy Markdown
Contributor Author

@AlexVelezLl @MisRob I have applied the #extension slot and addressed other feedback given, adding the brand example and tweaking the styles on the docs in a slightly opinionated way (open to feedback, mostly just colors of things on the toolbar examples).

One place that I've come to as I do regression testing/fixing on Kolibri is that the KToolbar currently has opinionated paddings and margins that require /deep/ styles in Kolibri to address.

Shall I slim that stuff down too while I'm at it? Basically, the slots will just be unpadded empty boxes for you to put your stuff in and pad/margin yourself to your heart's content.

This was Claude's assessment of the styles and their opinionated-ness and I agree with the assessment except on one point.

Rule Functional or opinionated?
.k-toolbar-main { display: flex; height: 3.5rem; padding: 0 16px } Functional — flex is required for slot layout, 3.5rem is the canonical Material height
.k-toolbar-extension { display: flex; align-items: center; min-height: 3rem; padding: 0 16px } Functional baseline (consumer can override via slot content)
.k-toolbar-left { display: flex; align-items: center; min-width: 0 } Functional — flex+min-width-0 are the truncation chain
.k-toolbar-left { margin-left: 16px } Opinionated — extra inset inside an already-padded row
.k-toolbar-leading-actions { margin-right: 8px; margin-left: -16px } Opinionated — exists only to counteract the previous opinion
.k-toolbar-nav { display: flex; align-items: baseline; margin-left: 16px; margin-right: 8px } Opinionated spacing
.k-toolbar-right { display: inline-block } Opinionated — and the one that forced Kolibri's /deep/ .k-toolbar-right { display: flex } workaround
.k-toolbar-title { min-width: 0; line-height: 1 } Functional — KTextTruncator alignment
.k-toolbar-brand { min-width: inherit } Functional

The min-height on the extensions is said to be default Material height but I'm dubious of using it here.

I'll be updating learningequality/kolibri#14618 with screenshots confirming that the updates work well there.


Also just noting that I realize the #extensions slot is a bit extending the scope here, but I'm also happy to take this PR as a chance to put some real polish on KToolbar if y'all think it'd be a worthwhile endeavour.

@nucleogenesis nucleogenesis marked this pull request as ready for review April 29, 2026 05:20
@MisRob

MisRob commented Apr 29, 2026

Copy link
Copy Markdown
Member

Thanks @nucleogenesis.

Basically, the slots will just be unpadded empty boxes for you to put your stuff in and pad/margin yourself to your heart's content.

Yes I think that'd be best. Thanks for raising it. We were just discussing it in another context. Generally I think that design system components shouldn't be opinionated in terms of using space around them (such as outer margins), as well as handling spacing within slots.

Also just noting that I realize the #extensions slot is a bit extending the scope here, but I'm also happy to take this PR as a chance to put some real polish on KToolbar if y'all think it'd be a worthwhile endeavour

Yes, thank you & I am happy to let you two decide what's the appropriate scope for this PR, depending on your bandwidth for this work and release schedule. Already lots of good work here :)

Removes the CSS rules that imposed Material-Design-style spacing on
KToolbar's internal slot wrappers — the same opinions that consumers
were previously forced to fight via /deep/ overrides. Slot composition
is still functional (flex layout, truncation chain stays intact); the
spacing decisions move to consumer slot content.

Removed rules:
  - .k-toolbar-left { margin-left: 16px }
  - .k-toolbar-leading-actions { margin-right: 8px; margin-left: -16px }
    (existed only to counteract the dropped left margin)
  - .k-toolbar-nav { display: flex; align-items: baseline;
                     margin-right: 8px; margin-left: 16px }
  - .k-toolbar-right { display: inline-block }
  - .k-toolbar-main { align-content: center }
    (no-op for single-line flex)

Kept (functional):
  - .k-toolbar-main flex/center/space-between/height/padding
  - .k-toolbar-extension flex/center/min-height/padding
  - .k-toolbar-left flex/center/min-width:0 (truncation chain)
  - .k-toolbar-brand min-width: inherit (truncation chain)
  - .k-toolbar-title min-width: 0; line-height: 1 (KTextTruncator alignment)

Visual impact on consumers using KToolbar today: the leading icon /
brand area / title / nav / trailing actions sit ~16-24px further left
than before (no longer pushed inward by the dropped margins). Native
KToolbar consumers in KDS' own examples render fine; the
NavigationAndActions example needed a small slot-local margin tweak on
its KRouterLinks (margin-right: 16px → margin: 0 8px) so the nav links
have breathing room from the title.

Downstream consumers (e.g. Kolibri's AppBar / ImmersiveToolbar /
LearningActivityBar) that were using /deep/ .k-toolbar-* selectors to
override these defaults can now drop those overrides outright — KDS
no longer has the opinions to fight.
@github-actions

Copy link
Copy Markdown
Contributor

Percy Visual Test Results

Percy Dashboard: View Detailed Report

Environment:

  • Node.js Version: 20.x
  • OS: Ubuntu-latest

Instructions for Reviewers:

  • Click on the Percy Dashboard link to view detailed visual diffs.
  • Review the visual changes highlighted in the report.
  • Approve or request changes based on the visual differences.

@AlexVelezLl AlexVelezLl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Code changes look good to me! Just one little question, and not approving just yet as I'm not sure if you plan to make further changes, but everything looks good to me at this point!

Comment thread lib/KToolbar/index.vue
.k-toolbar-extension {
display: flex;
align-items: center;
min-height: 3rem;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this a value we should enforce from KDS?

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.

nope 😅 will push some updates today

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.

3 participants