Skip to content

Commit

Permalink
feat: added badge
Browse files Browse the repository at this point in the history
  • Loading branch information
mwargan committed Sep 12, 2024
1 parent 0c36b8d commit cd32e30
Show file tree
Hide file tree
Showing 34 changed files with 642 additions and 9 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added __snapshots__/components-basebadge--busy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added __snapshots__/components-basebadge--contrast.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added __snapshots__/components-basebadge--default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added __snapshots__/components-basebadge--empty.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added __snapshots__/components-basebadge--outline.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added __snapshots__/components-basebadge--secondary.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added __snapshots__/components-tabnav--with-badge.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
136 changes: 136 additions & 0 deletions src/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ button {

/** Fix SVG color */
:where([role=button],
[role=status],
[type=button],
[type=file]::file-selector-button,
[type=reset],
Expand All @@ -208,6 +209,7 @@ button {
/* If the last element of the child is an SVG, remove the margin-right to keep the size consistent */
[aria-busy=true]:not(input, select, textarea, html):has(:last-child:is(svg))::before,
:where([role=button],
[role=status],
[type=button],
[type=file]::file-selector-button,
[type=reset],
Expand Down Expand Up @@ -411,6 +413,9 @@ article {
border-color: transparent;
border-radius: 0;

/* Do not wrap more than 2 lines */
white-space: nowrap;

}

.tab-nav button:focus {
Expand Down Expand Up @@ -443,4 +448,135 @@ article {

.dropdown:has(summary[aria-invalid=false])+small {
color: var(--pico-ins-color);
}

[role=status] {
display: inline-block;

padding: calc(var(--pico-form-element-spacing-vertical) / 5) calc(var(--pico-form-element-spacing-horizontal) / 3);

text-align: center;
vertical-align: text-top;
white-space: nowrap;

--pico-background-color: var(--pico-primary-background);
--pico-border-color: var(--pico-primary-border);
--pico-color: var(--pico-primary-inverse);
--pico-box-shadow: var(--pico-button-box-shadow, 0 0 0 rgba(0, 0, 0, 0));
--pico-line-height: 1;
--pico-font-size: 13px;

font-size: var(--pico-font-size);

line-height: var(--pico-line-height);

border: var(--pico-border-width) solid var(--pico-border-color);
border-radius: calc(var(--pico-border-radius) * 3);
outline: 0;
background-color: var(--pico-background-color);
box-shadow: var(--pico-box-shadow);
color: var(--pico-color);
font-weight: var(--pico-font-weight);

margin-left: 0.5ch;

/* Set max width to 4 letters */
max-width: 15ch;

/* When overflowing, the ellipses should be a + */
overflow: hidden;
text-overflow: ellipsis;

/* Max one line */
line-clamp: 1;
word-break: break-all;
}

[role=status] svg {
height: var(--pico-font-size);
width: var(--pico-font-size);
vertical-align: bottom;
}

/* On load the role=status should pop animate in to draw attention */
[role=status]:not([aria-busy=true]) {
animation: pop 0.2s ease-in-out;
}

@keyframes pop {
0% {
transform: scale(0.5);
}

80% {
transform: scale(1.2);
}

100% {
transform: scale(1);
}
}

[role=status][aria-busy=true] {
white-space: nowrap;
}

[role=status][aria-busy=true]::before {
height: var(--pico-font-size);
width: var(--pico-font-size);
vertical-align: bottom;
}

[role=status].secondary {
--pico-background-color: var(--pico-secondary-background);
--pico-border-color: var(--pico-secondary-border);
--pico-color: var(--pico-secondary-inverse);
}

[role=status].contrast {
--pico-background-color: var(--pico-contrast-background);
--pico-border-color: var(--pico-contrast-border);
--pico-color: var(--pico-contrast-inverse);
}

[role=status].notification {
--pico-background-color: var(--pico-del-color);
--pico-border-color: var(--pico-del-color);
--pico-color: var(--pico-contrast-inverse);
}

[role=status].outline {
--pico-background-color: transparent;
--pico-color: var(--pico-primary);
}

[role=status].outline.secondary {
--pico-color: var(--pico-secondary);
}

[role=status].outline.contrast {
--pico-color: var(--pico-contrast);
}

[role=status].outline.notification {
--pico-color: var(--pico-del-color);
}

[role=status][aria-busy=true]:not(.outline)::before {
/* Set the bg color to white */
filter: brightness(0) invert(1);
}

/* If the badge is empty, make it a circle */
[role=status]:empty {
border-radius: 50%;
height: 0.5rem;
width: 0.5rem;
padding: 0;
}

[role=status]:empty[aria-busy=true]::before {
height: 0.5rem;
width: 0.5rem;
vertical-align: bottom;
}
32 changes: 32 additions & 0 deletions src/components/BaseBadge.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script setup lang="ts">
import type { BadgeObject } from "@/types/badge";
import type { PropType } from "vue";
defineProps({
/**
* The aria-label for the badge. If not set and the value is numeric, it will default to `${value} New Notifications`.
*/
ariaLabel: {
type: String,
required: false,
default: "New Notification",
},
/** The class to use */
class: {
type: String as PropType<BadgeObject["className"]>,
required: false,
},
});
</script>
<template>
<span
role="status"
v-bind="$attrs"
:aria-label="ariaLabel"
:class="$props.class"
>
<!-- @slot This is the text or number to display in the badge -->
<slot />
</span>
</template>
4 changes: 4 additions & 0 deletions src/components/DropdownSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { useMultiselect } from "@/composables/useMultiselect";
import type { selectOption } from "@/types/listItem";
import { type PropType, computed, onMounted, ref, watch } from "vue";
import BaseBadge from "./BaseBadge.vue";
const props = defineProps({
/** The model value is an array of selected options. This element will always use an array of strings as the values, even if originally they are typed as numbers. This is to stay consistent with native HTML elements "value" behaviour. This may be changed in the future. */
Expand Down Expand Up @@ -382,6 +383,9 @@ defineExpose({ focus });
tabindex="0"
/>
{{ getLabel(option) }}
<base-badge v-if="option.badge !== undefined">{{
option.badge === true ? "" : option.badge
}}</base-badge>
</label>
</slot>
</li>
Expand Down
4 changes: 4 additions & 0 deletions src/components/TabNav.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import type { PropType } from "vue";
import BaseButton from "./BaseButton.vue";
import BaseBadge from "./BaseBadge.vue";
import type { selectOption } from "@/types/listItem";
import { useMultiselect } from "@/composables/useMultiselect";
Expand Down Expand Up @@ -58,6 +59,9 @@ const { normalisedOptions, getLabel, isOptionSelected, updateModelValue } =
:disabled="page.disabled"
>
{{ getLabel(page) }}
<base-badge v-if="page.badge !== undefined">{{
page.badge === true ? "" : page.badge
}}</base-badge>
</base-button>
</li>
</ul>
Expand Down
32 changes: 32 additions & 0 deletions src/components/__tests__/BaseBadge.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, expect, it } from "vitest";

import { mount } from "@vue/test-utils";

import BaseBadge from "../BaseBadge.vue";

describe("BaseBadge", () => {
it("renders properly", () => {
const wrapper = mount(BaseBadge);
// There should be an element with the role of status
expect(wrapper.find("[role=status]").exists()).toBe(true);
expect(wrapper.classes("outline")).toBe(false);
});
it("accepts a string input in its slot", () => {
const wrapper = mount(BaseBadge, {
slots: {
default: "Hello",
},
});
// The slot should be rendered
expect(wrapper.text()).toBe("Hello");
});
it("accepts a class name", () => {
const wrapper = mount(BaseBadge, {
props: {
className: "outline",
},
});
// The class name should be applied
expect(wrapper.classes("outline")).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ exports[`Dropdown Select > renders correctly 1`] = `
<ul data-v-10d9b766="">
<!--v-if-->
<!--v-if-->
<li data-v-10d9b766=""><label data-v-10d9b766=""><input data-v-10d9b766="" type="radio" tabindex="0" value="One"> One</label></li>
<li data-v-10d9b766=""><label data-v-10d9b766=""><input data-v-10d9b766="" type="radio" tabindex="0" value="Two"> Two</label></li>
<li data-v-10d9b766=""><label data-v-10d9b766=""><input data-v-10d9b766="" type="radio" tabindex="0" value="Three"> Three</label></li>
<li data-v-10d9b766=""><label data-v-10d9b766=""><input data-v-10d9b766="" type="radio" tabindex="0" value="One"> One
<!--v-if-->
</label></li>
<li data-v-10d9b766=""><label data-v-10d9b766=""><input data-v-10d9b766="" type="radio" tabindex="0" value="Two"> Two
<!--v-if-->
</label></li>
<li data-v-10d9b766=""><label data-v-10d9b766=""><input data-v-10d9b766="" type="radio" tabindex="0" value="Three"> Three
<!--v-if-->
</label></li>
<!--v-if-->
<!-- if the results are limited because of the visible limit, but there are more filteredResults, show a message -->
<!--v-if-->
Expand Down
20 changes: 15 additions & 5 deletions src/components/__tests__/__snapshots__/TabNav.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,21 @@
exports[`Tab navigation > renders correctly by default 1`] = `
"<nav aria-label="Tab Navigation" class="tab-nav">
<ul>
<li><button type="button" aria-label="Go to page 1" data-id="1" class="">Page 1</button></li>
<li><button type="button" aria-label="Go to page 2" data-id="2" class="">Page 2</button></li>
<li><button type="button" aria-label="Go to page 3" data-id="3" class="">Page 3</button></li>
<li><button type="button" aria-label="Go to page 4" data-id="4" class="">Page 4</button></li>
<li><button type="button" aria-label="Go to page 5" data-id="5" class="">Page 5</button></li>
<li><button type="button" aria-label="Go to page 1" data-id="1" class="">Page 1
<!--v-if-->
</button></li>
<li><button type="button" aria-label="Go to page 2" data-id="2" class="">Page 2
<!--v-if-->
</button></li>
<li><button type="button" aria-label="Go to page 3" data-id="3" class="">Page 3
<!--v-if-->
</button></li>
<li><button type="button" aria-label="Go to page 4" data-id="4" class="">Page 4
<!--v-if-->
</button></li>
<li><button type="button" aria-label="Go to page 5" data-id="5" class="">Page 5
<!--v-if-->
</button></li>
</ul>
</nav>"
`;
3 changes: 2 additions & 1 deletion src/helpers/normaliseOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const normaliseOptions = (
render: option.render,
disabled: option.disabled || false,
raw: option.raw,
badge: option.badge,
});
}
return normalisedOptions;
Expand All @@ -43,7 +44,7 @@ export const filterOptions = (
const optionValue = option[key];

// If the option value is a boolean, return all options
if (typeof optionValue === "boolean") {
if (typeof optionValue === "boolean" || typeof optionValue === "number") {
return optionValue.toString() === lowercaseValue;
}

Expand Down
Loading

0 comments on commit cd32e30

Please sign in to comment.