Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
cbe527f
added the ksnackbar and useksnackbar composable
Prashant-thakur77 Jan 27, 2026
119bb35
added test playground
Prashant-thakur77 Jan 27, 2026
ae9443e
updated ksnackbar
Prashant-thakur77 Jan 27, 2026
815480f
updated the styling
Prashant-thakur77 Jan 28, 2026
7ef6199
Refined
Prashant-thakur77 Feb 13, 2026
d973dd6
Refined
Prashant-thakur77 Feb 13, 2026
669ca6f
Refined
Prashant-thakur77 Feb 13, 2026
530f3ed
Updated the ksnacbar to handle kolibri and studio
Prashant-thakur77 Feb 13, 2026
7403f1d
CReayed proper folders for the ksnacbar and its composable
Prashant-thakur77 Feb 14, 2026
1f0389a
made test file for useksnakbar
Prashant-thakur77 Feb 14, 2026
7b06104
Made ksnackbar test file
Prashant-thakur77 Feb 14, 2026
acbfd11
Reverted playgorund
Prashant-thakur77 Feb 14, 2026
dabfc8f
ran lint
Prashant-thakur77 Feb 14, 2026
ba109c4
Made visual tests and documentation
Prashant-thakur77 Feb 23, 2026
2c999e2
refactor: Reorganize snackbar documentation
Prashant-thakur77 Mar 1, 2026
40e2236
Adding documentation
Prashant-thakur77 Mar 1, 2026
d4a6187
Add KSnackbar and useKSnackbar to documentation navigation
Prashant-thakur77 Mar 1, 2026
662f76a
Complete KSnackbar implementation
Prashant-thakur77 Mar 1, 2026
8f21328
Added z index for the ksnabar docs page
Prashant-thakur77 Mar 2, 2026
bdbc84a
AddeFInalizing the examples
Prashant-thakur77 Mar 2, 2026
4ea03c7
UPdated
Prashant-thakur77 Mar 2, 2026
40cec4c
UPdated
Prashant-thakur77 Mar 2, 2026
3e0dc1e
Visual tests
Prashant-thakur77 Mar 2, 2026
92b426c
Finalixing the tests
Prashant-thakur77 Mar 2, 2026
1990b00
fixing lint
Prashant-thakur77 Mar 2, 2026
7d2288b
Alinment
Prashant-thakur77 Mar 2, 2026
c983116
Updating the ksnabar
Prashant-thakur77 Mar 2, 2026
daca2ed
Added the onblur and autofocus functionality
Prashant-thakur77 Mar 2, 2026
5178d36
updated to match the existing chnages
Prashant-thakur77 Mar 3, 2026
e6ab040
Corrected index
Prashant-thakur77 Mar 3, 2026
c187bf8
made the tes prop required
Prashant-thakur77 Mar 3, 2026
3e2c3a8
made the isopen prop required
Prashant-thakur77 Mar 3, 2026
3caec30
done
Prashant-thakur77 Mar 8, 2026
8a4bee5
Removed click event
Prashant-thakur77 Mar 8, 2026
600068e
removed click event test
Prashant-thakur77 Mar 8, 2026
b33d254
lintign
Prashant-thakur77 Mar 8, 2026
5161a1e
Style: updated the ksnacbar with new specifications provided
Prashant-thakur77 Mar 25, 2026
cbcff2f
Added the helper function to not let repeating the snacbar options
Prashant-thakur77 Mar 25, 2026
3cc2990
Refactor: use KFocusTrap, move useKLiveRegion into setup, replace onB…
Prashant-thakur77 Mar 25, 2026
7038c63
Refactor: RTL via CSS dir selector, gap for action spacing, remove ne…
Prashant-thakur77 Mar 25, 2026
ade6225
rename actionClick event, merge watchers, extract onOpen/onClose helpers
Prashant-thakur77 Mar 25, 2026
6f1a978
remove unused destructuring in Persistent.vue example
Prashant-thakur77 Mar 25, 2026
2bc9dd9
Fix: revert event name to action-click for Vue 2 compatibility
Prashant-thakur77 Mar 25, 2026
a4a0f31
Just playgroung for testing
Prashant-thakur77 Mar 26, 2026
56352bb
destructured the examples and added the native onblur handler
Prashant-thakur77 Mar 26, 2026
9d7bf8e
Reverting the playground
Prashant-thakur77 Mar 26, 2026
18cff53
Finalizing the components according to the review
Prashant-thakur77 Mar 26, 2026
090d612
SYmmetric padding in the rtl
Prashant-thakur77 Mar 26, 2026
415ff54
Updated files according to the review
Prashant-thakur77 Mar 31, 2026
31bce7a
lint: ran lint command and fixed a test failure
Prashant-thakur77 Mar 31, 2026
91fccd5
check
Prashant-thakur77 Mar 31, 2026
e172d12
check
Prashant-thakur77 Mar 31, 2026
55442ca
check
Prashant-thakur77 Mar 31, 2026
cee36f1
updated
Prashant-thakur77 Apr 1, 2026
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
96 changes: 96 additions & 0 deletions docs/pages/ksnackbar.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.

This is just a feeling, and @MisRob can confirm, but I feel that the KSnackbar page should show the instructions to set up the snackbars on the app, and useKSnackbar should show the instructions of all available tools we have for displaying snackbars (e.g. forceReuse, backdrop, and all examples we have right now in the KSnackbar page), since at the end, it is the composable is tool we will use to actually show the snackbars, not KSnackbar; KSnackbar just acts as a required step to enable the usage of useKSnackbar.

And in the end, I think people would use the useKSnackbar page more, since this is what we as developers will use most; we won't interact with KSnackbar much after its initial setup.

So, I'd say that a good structure would be:
KSnackbar page:

  • Instructions for initial setup.
  • Link to the useKSnackbar flagging that it is the composable the responsible for every interaction with the snackbars.

useKSnackbar page:

  • Link to KSnackbar saying it is required to have it set up before using this composable.
  • All examples and things we can do with snackbars.

Now, on a completely different note (and a question more for @MisRob): Do we really need to expose all current KSnackbar fields? Do we want to leave the option for people to use snackbars in a different way without useKSnackbar? Because the way it is implemented right now, KSnackbar is a completely independent, abstract component, and useKSnackbar acts just as a global store for passing the information to a global KSnackbar that should be set up on the app, but really, KSnackbar can be used independently of useKSnackbar.

The answer for this will depend on how restrictive/flexible we want to be, but I just want to make sure we are following a path consciously, and not just because that's how it was done before. If we keep with this flexible path, though, it'd be great to have some notes saying that people can use it independently if they have more advanced use cases, perhaps? (I can only imagine use cases where we would need to pass a slot to KSnackbar and display, for example, a KIcon next to the text). If not, we can just wire KSnackbar with useKSnackbar, and call useKSnackbar directly inside KSnackbar; this way, we wouldn't need to expose these fields on KSnackbar, and its setup would only be calling <KSnackbar />.

Copy link
Copy Markdown
Contributor Author

@Prashant-thakur77 Prashant-thakur77 Mar 26, 2026

Choose a reason for hiding this comment

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

This makes a lot of sense! I'll wait for @MisRob to weigh in, but I'm happy to reorganize once we have a decision.

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.

Hi, thanks a lot, those are good considerations. I saw then note and will follow-up :)

Copy link
Copy Markdown
Member

@MisRob MisRob Apr 2, 2026

Choose a reason for hiding this comment

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

I'm sorry it took such a long time ~ I needed to check a bit on the current state & wanted to think through it. Thanks again for bringing this @AlexVelezLl, it's excellent points & taking time to chat about it now will save us from future trouble. Let me show a possible direction in a very simplified snippet & let see what you think (I only checked the implementation very high-level) What about something like this?

// App.vue
// no `:isOpen="snackbarIsVisible"` and other bindings needed here
// shows when `useKSnackbar.createSnackbar` called from anywhere in the app

<KSnackbar />

// Component1.vue
// contacts global `KSnackbar` installed in App.vue (default & most common behavior)
// other `useKSnackbar` methods & attributes also by default affect the global instance

setup() {
  const { createSnackbar } = useKSnackbar();
  createSnackbar({ text: ... });
}

// Component2.vue
// doesn't want to use the global snackbar (utilize `:isOpen="snackbarIsVisible"` and other bindings if needed)

<KSnackbar :isOpen="snackbarIsVisible" ... >
  <template #text="{ text }"><KIcon/ >{{ text }}</template>
</KSnackbar />


setup() {
  const { createSnackbar, snackbarIsVisible } = useKSnackbar(global=false);
  createSnackbar({ text: ... });
}

As far as I'm aware, most commonly we will just use the global snackbar so that should be the default behavior and it'd be good if it'd be simple & straightforward to setup. At the same time, it's always a good idea to design API in a way that will scale well. And it seems that this PR is already fairly well setup for this?

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.

And @AlexVelezLl, your suggestion around documentation makes sense to me :)

@Prashant-thakur77 I think you can just make sure that basic documentation structure is setup as per Alex's guidance, but (unless you're specifically interested in technical writing :) no need to spend much time on details. I usually do last round on our docs myself so that it's consistent overall & it's easier for me to tweak directly rather than to post many comments. So just working towards some good initial shape for now will do!

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.

We can also combine the layered composables and the global flag and leave the useKLocalSnackbar private, and we could have:

function _useLocalKSnackbar() {
  // local state defined
  return {
    // ... state, actions, etc.
  };
}

const globalSnackbar = _useLocalKSnackbar();
export function useKSnackbar(global=true) {
  if (global) {
    return globalSnackbar;
  }
  return _useLocalKSnackbar();
}

Copy link
Copy Markdown
Member

@MisRob MisRob Apr 9, 2026

Choose a reason for hiding this comment

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

The main benefit of the two KDS composables you suggest Alex would be that we don't need to set the global one in each app. So perhaps that'd be the best argument for them ;) In addition to what you mentioned. From that point of view, yes sound like a nice option to me.

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.

@AlexVelezLl, I posted my comment ^ before I saw yours :) We were both probably writing at the same time & the page didn't refresh.

We can also combine the layered composables and the global flag..

Unless you see some strong benefit, I think I would prefer to not go with the global flag I originally suggested, compared to the both later options I consider it the weakest one for a number of reasons.

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.

Unless you see some strong benefit, I think I would prefer to not go with the global flag I originally suggested, compared to the both later options I consider it the weakest one for a number of reasons.

No strong preferences, hehe! 😅 Just wanted to describe another alternative.

Alright, so @Prashant-thakur77, apologies for the delay. We will go in the direction of exposing both useKSnackbar and useKLocalSnackbar from KDS. I'd say both composables can live under the same useKSnackbar file, to make the maintenance more straightforward, and then, this module will expose useKSnackbar as default, to be consistent with other composables exposed from KDS, and we can expose useKLocalSnackbar as a named export. This is mostly because the 99.99% of the time, we will be using just useKSnackbar, and useKLocalSnackbar would be rather a special case.

For documentation, let's just include everything on useKSnackbar as we agreed previously here, but let's add a section pointing to the existence of useKLocalSnackbar in case a more granular control over KSnackbar is needed (e.g., using a for the snackbar text). Overall, the external API and installation process will still be the same; the only change for consumers is that they can now use useKLocalSnackbar.

If we are missing something, please let us know!

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.

Sounds like a plan :) Go for it & thanks everyone.

Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<template>

<DocsPageTemplate apiDocs>
<DocsPageSection
title="Overview"
anchor="#overview"
>
<p>
The <code>KSnackbar</code> component provides a globally-managed notification system for
displaying non-critical messages to users. It supports action buttons, custom timing, focus
management, and full keyboard accessibility.
</p>
<p>
When multiple snackbars are triggered, new messages automatically replace the current one
with a smooth transition. For status updates that need to change text without animation, use
<code>forceReuse</code>.
</p>
<ul>
<li>Global notification state via the <code>useKSnackbar</code> composable</li>
<li>Optional action button for quick follow-up actions</li>
<li>Auto-hide with configurable duration (or persistent mode)</li>
<li>Backdrop mode for higher-priority messages</li>
<li>Bottom offset support for layouts with bottom navigation</li>
</ul>
</DocsPageSection>

<DocsPageSection
title="Usage"
anchor="#usage"
>
<p>
The <code>KSnackbar</code> component serves only as the root-level mount point for
snackbars. Developers should <strong>not</strong> interact with this component directly to
show messages.
</p>

<h3>Global Setup</h3>
<p>
Place a single <code>KSnackbar</code> component in your application's root template (e.g.,
<code>App.vue</code>) and bind it to the state provided by the
<DocsInternalLink
text="useKSnackbar"
href="/useksnackbar"
/>
composable.
</p>

<!-- eslint-disable -->
<!-- prettier-ignore -->
<DocsShowCode language="html">
<KSnackbar
:isOpen="snackbarIsVisible"
:text="snackbarOptions.text"
:actionText="snackbarOptions.actionText"
:bottomOffset="snackbarOptions.bottomOffset"
:backdrop="snackbarOptions.backdrop"
:autofocus="snackbarOptions.autofocus"
:autoDismiss="snackbarOptions.autoDismiss"
:duration="snackbarOptions.duration"
@action-click="handleActionClick"
@blur="handleBlur"
@close="clearSnackbar"
/>
</DocsShowCode>
<!-- eslint-enable -->

<p>
For interactive examples (Basic, With Action, Persistent, Force Reuse, etc.), please see the
<DocsInternalLink
text="useKSnackbar"
href="/useksnackbar"
/>
composable page.
</p>
</DocsPageSection>
</DocsPageTemplate>

</template>


<script>

export default {
name: 'DocsKSnackbar',
};

</script>


<style lang="scss" scoped>

::v-deep .k-snackbar-wrapper {
z-index: 100;
}

</style>
288 changes: 288 additions & 0 deletions docs/pages/useksnackbar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
<template>

<DocsPageTemplate apiDocs>
<DocsPageSection
title="Overview"
anchor="#overview"
>
<p>
A composable that offers the <code>createSnackbar</code>, <code>clearSnackbar</code>, and
<code>setSnackbarText</code> functions, as well as the reactive
<code>snackbarIsVisible</code> and <code>snackbarOptions</code> refs. It is used to manage a
global snackbar state, allowing any component to trigger a snackbar without having to pass
props deeply.
</p>
</DocsPageSection>

<DocsPageSection
title="Usage"
anchor="#usage"
>
<p>
Before using this composable to show messages, ensure you have mounted the root component.
For instructions, please see the
<DocsInternalLink
text="KSnackbar"
href="/ksnackbar"
/>
global setup.
</p>

<h3>Examples</h3>

<h4>Basic snackbar</h4>
<p>Use the default behavior for short confirmation messages.</p>
<DocsExample
loadExample="KSnackbar/Basic.vue"
exampleId="basic"
block
/>

<h4>Snackbar with action</h4>
<p>
Provide <code>actionText</code> and <code>actionCallback</code> when calling
<code>createSnackbar()</code>
to enable an immediate action such as Undo. The callback is stored in the composable and
executed when the user clicks the action button.
</p>
<DocsExample
loadExample="KSnackbar/WithAction.vue"
exampleId="with-action"
block
/>

<h4>Persistent snackbar</h4>
<p>
Set <code>autoDismiss: false</code> in <code>createSnackbar()</code> to disable auto-hide
for important messages. Alternatively, set <code>duration: 0</code> to achieve the same
effect.
</p>
<DocsExample
loadExample="KSnackbar/Persistent.vue"
exampleId="persistent"
block
/>

<h4>Snackbar with bottom offset</h4>
<p>Use <code>bottomOffset</code> when a bottom navigation bar or fixed footer is present.</p>
<DocsExample
loadExample="KSnackbar/WithBottomOffset.vue"
exampleId="with-bottom-offset"
block
/>

<h4>Update snackbar without transition</h4>
<p>
Use <code>forceReuse</code> to update the snackbar text in place without replaying the
transition animation. Useful for status updates like connection state changes.
</p>
<DocsExample
loadExample="KSnackbar/ForceReuse.vue"
exampleId="force-reuse"
block
/>

<h4>Snackbar with autofocus</h4>
<p>
Set <code>autofocus: true</code> to immediately focus the action button when the snackbar
appears. Useful for critical actions that need immediate attention.
</p>
<DocsExample
loadExample="KSnackbar/WithAutofocus.vue"
exampleId="with-autofocus"
block
/>

<h4>Snackbar with onBlur handling</h4>
<p>
Provide an <code>onBlur</code> callback to handle advanced focus management scenarios, such
as auto-dismissing when the user tabs away or clicks elsewhere.
</p>
<DocsExample
loadExample="KSnackbar/WithOnBlur.vue"
exampleId="with-onblur"
block
/>
</DocsPageSection>

<DocsPageSection
title="Parameters"
anchor="#parameters"
>
<p>
<code>createSnackbar</code> accepts an <code>options</code> object with the following
properties:
</p>
<PropsTable :api="options" />
</DocsPageSection>

<DocsPageSection
title="Related"
anchor="#related"
>
<ul>
<li><DocsLibraryLink component="KSnackbar" /> for the snackbar component</li>
<li>
<DocsInternalLink
text="Snackbars"
href="/snackbars"
/>
has design guidelines and usage guidance
</li>
</ul>
</DocsPageSection>

<!-- Global snackbar instance for all examples on this page -->
<KSnackbar
:isOpen="snackbarIsVisible"
:text="snackbarOptions.text"
:actionText="snackbarOptions.actionText"
:bottomOffset="snackbarOptions.bottomOffset"
:backdrop="snackbarOptions.backdrop"
:autofocus="snackbarOptions.autofocus"
:autoDismiss="snackbarOptions.autoDismiss"
:duration="snackbarOptions.duration"
@action-click="handleActionClick"
@blur="handleBlur"
@close="clearSnackbar"
/>
</DocsPageTemplate>

</template>


<script>

import PropsTable from '../common/DocsPageTemplate/jsdocs/PropsTable';
import useKSnackbar from '../../lib/composables/useKSnackbar';

export default {
components: {
PropsTable,
},
setup() {
const { snackbarIsVisible, snackbarOptions, clearSnackbar } = useKSnackbar();

const handleActionClick = () => {
if (snackbarOptions.value.actionCallback) {
snackbarOptions.value.actionCallback();
}
clearSnackbar();
};

const handleBlur = () => {
if (typeof snackbarOptions.value.onBlur === 'function') {
snackbarOptions.value.onBlur();
}
};

return {
snackbarIsVisible,
snackbarOptions,
clearSnackbar,
handleActionClick,
handleBlur,
};
},
data() {
return {
options: [
{
name: 'text',
required: true,
type: { name: 'string' },
description: 'The text to display inside the snackbar.',
},
{
name: 'actionText',
required: false,
default: "''",
type: { name: 'string' },
description: 'Optional text for an action button (e.g. "Undo").',
},
{
name: 'actionCallback',
required: false,
default: 'null',
type: { name: 'function' },
description:
'Function stored in composable and called when the action button is clicked. Retrieved via @action-click event handler at the app root level.',
},
{
name: 'duration',
required: false,
default: '5000',
type: { name: 'number' },
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

blocking: The documented default for duration here is 4000, but the composable's _getSnackbarOptions (line 8) and the component prop (line 272 of KSnackbar.vue) both default to 5000. Update this to 5000 to match the implementation.

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.

Updated to 5000

description: 'Time in ms until the snackbar auto-hides. Set to 0 to disable auto-hide.',
},
{
name: 'autoDismiss',
required: false,
default: 'true',
type: { name: 'boolean' },
description:
'Whether the snackbar should auto-dismiss after the duration. More semantic than setting duration to 0.',
},
{
name: 'bottomOffset',
required: false,
default: '0',
type: { name: 'number' },
description:
'Additional bottom offset in pixels. Useful when a bottom navigation bar is present.',
},
{
name: 'backdrop',
required: false,
default: 'false',
type: { name: 'boolean' },
description:
'If true, shows a darkening backdrop behind the snackbar. Also makes the snackbar announce assertively instead of politely for screen readers.',
},
{
name: 'autofocus',
required: false,
default: 'false',
type: { name: 'boolean' },
description:
'If true, autofocuses the action button when the snackbar appears. Improves accessibility for critical actions.',
},
{
name: 'onBlur',
required: false,
default: 'null',
type: { name: 'function' },
description:
'Blur event handler for when the action button loses focus. Useful for advanced focus management.',
},
{
name: 'forceReuse',
required: false,
default: 'false',
type: { name: 'boolean' },
description:
'When true, updates the current snackbar text in place without replaying the transition animation. Useful for status updates like connection state changes.',
},
{
name: 'hideCallback',
required: false,
default: 'null',
type: { name: 'function' },
description:
'Function called when the snackbar is hidden or replaced. Useful for cleanup or promise resolution.',
},
],
};
},
};

</script>


<style lang="scss" scoped>

::v-deep .k-snackbar-wrapper {
z-index: 100;
}

</style>
Loading
Loading