@kiva/kv-components 3.41.0 → 3.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,39 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [3.43.0](https://github.com/kiva/kv-ui-elements/compare/@kiva/kv-components@3.42.0...@kiva/kv-components@3.43.0) (2023-09-15)
7
+
8
+
9
+ ### Features
10
+
11
+ * added label support ([c016a68](https://github.com/kiva/kv-ui-elements/commit/c016a683ae38ca5af66fa86576d88abd0066d719))
12
+ * line graph can now just take values and normalization happens in component ([3f706be](https://github.com/kiva/kv-ui-elements/commit/3f706be31b288b64e4a566746bd7d0530ce25650))
13
+ * ported over Shua's line graph using clip path and made slight CSS tweaks to match mocks ([56ac312](https://github.com/kiva/kv-ui-elements/commit/56ac312924eecdf56e2d9d99082643e6d42f7765))
14
+ * simple unit tests and small fixes related to tests ([43700fa](https://github.com/kiva/kv-ui-elements/commit/43700faf6c1f0c76432107c3a658539bd3bf6c7b))
15
+
16
+
17
+
18
+
19
+
20
+ # [3.42.0](https://github.com/kiva/kv-ui-elements/compare/@kiva/kv-components@3.41.0...@kiva/kv-components@3.42.0) (2023-09-14)
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * added event emitters ([3a84e6e](https://github.com/kiva/kv-ui-elements/commit/3a84e6e97b5d230ff747c296339bce31263d89da))
26
+ * added event emitters, addressed comments, updated tests ([35a8a0c](https://github.com/kiva/kv-ui-elements/commit/35a8a0c6b34eed36fba030c29d3389c356c06927))
27
+ * addressing build issues, made button square ([974bbdc](https://github.com/kiva/kv-ui-elements/commit/974bbdcac2558fc47c7280f6535d390ed8c824ef))
28
+ * stopped requiring default title ([f494700](https://github.com/kiva/kv-ui-elements/commit/f494700078a2660f72703b95327df6e407d16d5f))
29
+
30
+
31
+ ### Features
32
+
33
+ * new edit button that opens up a lightbox for editing page settings in kiva at work pages ([9d9c2ca](https://github.com/kiva/kv-ui-elements/commit/9d9c2caf3df0f741c2039d2423e42e108e753ff0))
34
+
35
+
36
+
37
+
38
+
6
39
  # [3.41.0](https://github.com/kiva/kv-ui-elements/compare/@kiva/kv-components@3.40.1...@kiva/kv-components@3.41.0) (2023-09-07)
7
40
 
8
41
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kiva/kv-components",
3
- "version": "3.41.0",
3
+ "version": "3.43.0",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -75,5 +75,5 @@
75
75
  "optional": true
76
76
  }
77
77
  },
78
- "gitHead": "e8c825179c7a3044372369640113d65d7c30b3f5"
78
+ "gitHead": "023c2d950460478d6c3684d3d09906f9d9672f35"
79
79
  }
@@ -0,0 +1,71 @@
1
+ import { render, fireEvent } from '@testing-library/vue';
2
+ import { axe } from 'jest-axe';
3
+ import { nextTick } from 'vue';
4
+ import addVueRouter from '../../utils/addVueRouter';
5
+ import KvEditButton from '../../../../vue/KvEditButton.vue';
6
+
7
+ describe('KvEditButton', () => {
8
+ const renderComponent = (options) => render(KvEditButton, addVueRouter(options));
9
+
10
+ it('renders the edit button', () => {
11
+ const { getByRole } = renderComponent();
12
+ getByRole('button', { name: /edit/i });
13
+ });
14
+
15
+ it('shows the lightbox when the edit button is clicked', async () => {
16
+ const { getByRole } = renderComponent();
17
+ const button = getByRole('button', { name: /edit/i });
18
+ await fireEvent.click(button);
19
+ expect(getByRole('dialog')).toBeVisible();
20
+ });
21
+
22
+ it('closes the lightbox when the close event is emitted', async () => {
23
+ const { getByRole, queryByRole } = renderComponent();
24
+ const button = getByRole('button', { name: /edit/i });
25
+ await fireEvent.click(button);
26
+ await fireEvent.keyUp(document, { key: 'Escape', code: 'Escape' });
27
+ await nextTick();
28
+ expect(queryByRole('dialog')).not.toBeInTheDocument();
29
+ });
30
+
31
+ it('renders a custom title for the lightbox if provided', () => {
32
+ const { getByText } = renderComponent({ props: { title: 'Custom Title' } });
33
+ getByText('Custom Title');
34
+ });
35
+
36
+ it('has no automated accessibility violations', async () => {
37
+ const { container } = renderComponent();
38
+ const results = await axe(container);
39
+ expect(results).toHaveNoViolations();
40
+ });
41
+
42
+ it('renders content in the default slot of the lightbox', async () => {
43
+ const customContent = 'Custom Content for Body';
44
+ const { getByText, getByRole } = renderComponent({
45
+ slots: { default: customContent },
46
+ });
47
+ const button = getByRole('button', { name: /edit/i });
48
+ await fireEvent.click(button);
49
+ expect(getByText(customContent)).toBeVisible();
50
+ });
51
+
52
+ it('renders content in the header slot of the lightbox', async () => {
53
+ const customHeader = 'Custom Header Content';
54
+ const { getByText, getByRole } = renderComponent({
55
+ slots: { header: customHeader },
56
+ });
57
+ const button = getByRole('button', { name: /edit/i });
58
+ await fireEvent.click(button);
59
+ expect(getByText(customHeader)).toBeVisible();
60
+ });
61
+
62
+ it('renders content in the controls slot of the lightbox', async () => {
63
+ const customControls = 'Custom Controls Content';
64
+ const { getByText, getByRole } = renderComponent({
65
+ slots: { controls: customControls },
66
+ });
67
+ const button = getByRole('button', { name: /edit/i });
68
+ await fireEvent.click(button);
69
+ expect(getByText(customControls)).toBeVisible();
70
+ });
71
+ });
@@ -0,0 +1,58 @@
1
+ import { render } from '@testing-library/vue';
2
+ import { axe } from 'jest-axe';
3
+ import KvLineGraph from '../../../../vue/KvLineGraph.vue';
4
+
5
+ describe('KvLineGraph', () => {
6
+ const points = [
7
+ { value: 10 },
8
+ { value: 20 },
9
+ { value: 50 },
10
+ { value: 60 },
11
+ { value: 80 },
12
+ ];
13
+
14
+ const pointsWithLabels = [
15
+ { value: 10, label: '2014' },
16
+ { value: 20, label: '2015' },
17
+ { value: 50, label: '2016' },
18
+ { value: 60, label: '2017' },
19
+ { value: 80, label: '2018' },
20
+ ];
21
+
22
+ it('should render chart with points', () => {
23
+ const { container } = render(KvLineGraph, { props: { points } });
24
+ const pointElements = container.querySelectorAll('span');
25
+
26
+ expect(pointElements.length).toBe(points.length);
27
+ });
28
+
29
+ it('should render chart with value labels', () => {
30
+ const { container } = render(KvLineGraph, { props: { points: pointsWithLabels } });
31
+ const pointElements = container.querySelectorAll('span');
32
+
33
+ expect(pointElements.length).toBe(points.length * 2);
34
+ });
35
+
36
+ it('should render chart with x-axis label', () => {
37
+ const { container } = render(KvLineGraph, { props: { points, axisLabel: 'People supported over time' } });
38
+ const label = container.querySelectorAll('h4');
39
+
40
+ expect(label.length).toBe(1);
41
+ });
42
+
43
+ it('should render chart with value labels and x-axis label', () => {
44
+ const { container } = render(KvLineGraph, { props: { points: pointsWithLabels, axisLabel: 'People supported over time' } });
45
+ const pointElements = container.querySelectorAll('span');
46
+ const label = container.querySelectorAll('h4');
47
+
48
+ expect(pointElements.length).toBe(points.length * 2);
49
+ expect(label.length).toBe(1);
50
+ });
51
+
52
+ it('should have no automated accessibility violations', async () => {
53
+ const { container } = render(KvLineGraph, { props: { points } });
54
+ const results = await axe(container);
55
+
56
+ expect(results).toHaveNoViolations();
57
+ });
58
+ });
@@ -0,0 +1,105 @@
1
+ <template>
2
+ <div>
3
+ <!-- Edit Button -->
4
+ <KvButton
5
+ variant="secondary"
6
+ aria-label="Edit"
7
+ class="tw-w-7 tw-h-10"
8
+ @click="showLightbox"
9
+ >
10
+ <KvMaterialIcon
11
+ class="tw-w-3 tw-h-3"
12
+ :icon="mdiPencil"
13
+ />
14
+ </KvButton>
15
+
16
+ <!-- Lightbox -->
17
+ <KvLightbox
18
+ :visible="isVisible"
19
+ :title="title"
20
+ :prevent-close="preventClose"
21
+ :variant="variant"
22
+ @lightbox-closed="hideLightbox"
23
+ >
24
+ <template #header>
25
+ <slot name="header"></slot>
26
+ </template>
27
+
28
+ <!-- default slot (body) -->
29
+ <slot></slot>
30
+
31
+ <!-- controls slot -->
32
+ <template #controls>
33
+ <slot
34
+ name="controls"
35
+ :hideLightbox="hideLightbox"
36
+ ></slot>
37
+ </template>
38
+ </KvLightbox>
39
+ </div>
40
+ </template>
41
+
42
+ <script>
43
+ import { ref } from 'vue-demi';
44
+ import { mdiPencil } from '@mdi/js';
45
+ import KvButton from './KvButton.vue';
46
+ import KvMaterialIcon from './KvMaterialIcon.vue';
47
+ import KvLightbox from './KvLightbox.vue';
48
+
49
+ export default {
50
+ components: {
51
+ KvButton,
52
+ KvMaterialIcon,
53
+ KvLightbox,
54
+ },
55
+ props: {
56
+ variant: {
57
+ type: String,
58
+ default: 'lightbox',
59
+ validator(value) {
60
+ return ['lightbox', 'alert'].includes(value);
61
+ },
62
+ },
63
+ /**
64
+ * The title of the dialog which describes the dialog to screenreaders, and if no
65
+ * content is in the `header` slot, will be displayed at the top of the lightbox.
66
+ * */
67
+ title: {
68
+ type: String,
69
+ default: '',
70
+ },
71
+ /**
72
+ * The dialog has no close X button, clicking the screen does not close,
73
+ * pressing ESC does not close.
74
+ * */
75
+ preventClose: {
76
+ type: Boolean,
77
+ default: false,
78
+ },
79
+ },
80
+ emits: [
81
+ 'lightbox-opened',
82
+ 'lightbox-closed',
83
+ ],
84
+ setup(_, { emit }) {
85
+ const isVisible = ref(false);
86
+
87
+ const showLightbox = () => {
88
+ isVisible.value = true;
89
+ emit('lightbox-opened');
90
+ };
91
+
92
+ const hideLightbox = () => {
93
+ isVisible.value = false;
94
+ emit('lightbox-closed');
95
+ };
96
+
97
+ return {
98
+ mdiPencil,
99
+ isVisible,
100
+ showLightbox,
101
+ hideLightbox,
102
+ };
103
+ },
104
+ };
105
+ </script>
@@ -0,0 +1,128 @@
1
+ <template>
2
+ <div class="tw-h-full tw-w-full tw-p-2.5">
3
+ <figure
4
+ class="tw-w-full tw-relative"
5
+ :style="{ height: graphHeight }"
6
+ >
7
+ <div
8
+ class="tw-w-full tw-h-full tw-bg-marigold-2 tw-opacity-low"
9
+ :style="{ clipPath: `polygon(${shade}, 100% 100%, 0% 100%)` }"
10
+ ></div>
11
+ <div
12
+ class="tw-absolute tw-top-0 tw-w-full tw-h-full tw-bg-marigold-2"
13
+ :style="{ clipPath: `polygon(${line})` }"
14
+ ></div>
15
+ <span
16
+ v-for="point in normalizedPoints"
17
+ :key="point.x"
18
+ class="
19
+ tw-absolute
20
+ tw-w-2
21
+ tw-h-2
22
+ tw-border
23
+ tw-border-white
24
+ tw-bg-marigold-2
25
+ tw-rounded-full
26
+ "
27
+ :style="{ left: `${point.x}%`, top: `${point.y}%`, transform: 'translate(-50%, -50%)' }"
28
+ ></span>
29
+ <template v-for="point in normalizedPoints">
30
+ <span
31
+ v-if="point.label"
32
+ :key="point.label"
33
+ class="tw-absolute"
34
+ :style="{ left: `${point.x}%`, bottom: '-3rem', transform: 'translate(-50%, -50%)' }"
35
+ >
36
+ {{ point.label }}
37
+ </span>
38
+ </template>
39
+ </figure>
40
+ <h4
41
+ v-if="axisLabel"
42
+ class="tw-text-center"
43
+ :class="{ 'tw-pt-1': !hasValueLabels, 'tw-pt-6': hasValueLabels }"
44
+ >
45
+ {{ axisLabel }}
46
+ </h4>
47
+ </div>
48
+ </template>
49
+
50
+ <script>
51
+ import { computed, toRefs } from 'vue-demi';
52
+
53
+ export default {
54
+ props: {
55
+ /**
56
+ * Array of objects like [{ value: 10, label: '2014' }, { value: 20, label: '2015' }]
57
+ */
58
+ points: {
59
+ type: Array,
60
+ required: true,
61
+ },
62
+ /**
63
+ * The optional label to show below the graph on the x-axis
64
+ */
65
+ axisLabel: {
66
+ type: String,
67
+ default: '',
68
+ },
69
+ },
70
+ setup(props) {
71
+ const { points, axisLabel } = toRefs(props);
72
+
73
+ // Get step to use on x-axis
74
+ const xIncrement = Math.round(100 / (points.value.length - 1));
75
+
76
+ // Find the largest value to be used as the scale of the graph
77
+ const largestY = computed(() => {
78
+ return points.value.reduce((prev, current) => {
79
+ return prev > current.value ? prev : current.value;
80
+ }, 0);
81
+ });
82
+
83
+ // Find the largest value to be used as the scale of the graph
84
+ const hasValueLabels = computed(() => {
85
+ return points.value.reduce((prev, current) => {
86
+ return prev || !!current.label;
87
+ }, false);
88
+ });
89
+
90
+ // Convert single values to points using increment and largest value
91
+ const normalizedPoints = computed(() => {
92
+ return points.value.reduce((prev, next, i) => {
93
+ prev.push({
94
+ x: i * xIncrement,
95
+ y: 100 - ((next.value / largestY.value) * 100),
96
+ label: next.label,
97
+ });
98
+
99
+ return prev;
100
+ }, []);
101
+ });
102
+
103
+ // Used for drawing the shading under the line
104
+ const shade = computed(() => (normalizedPoints.value.map(({ x, y }) => `${x}% ${y}%`).join(',')));
105
+
106
+ // Used for drawing the line
107
+ const line = computed(() => {
108
+ const topLine = normalizedPoints.value.map(({ x, y }) => `${x}% ${y + 0.3}%`).join(',');
109
+ const bottomLine = [...normalizedPoints.value].reverse().map(({ x, y }) => `${x}% ${y - 0.3}%`).join(',');
110
+ return `${topLine}, ${bottomLine}`;
111
+ });
112
+
113
+ const graphHeight = computed(() => {
114
+ const labelSpace = (axisLabel.value ? 2 : 0) + (hasValueLabels.value ? 2 : 0);
115
+
116
+ return `calc(100% - ${labelSpace}rem)`;
117
+ });
118
+
119
+ return {
120
+ hasValueLabels,
121
+ graphHeight,
122
+ normalizedPoints,
123
+ shade,
124
+ line,
125
+ };
126
+ },
127
+ };
128
+ </script>
@@ -0,0 +1,63 @@
1
+ import KvEditButton from '../KvEditButton.vue';
2
+ import KvButton from '../KvButton.vue';
3
+
4
+ export default {
5
+ title: 'KvEditButton',
6
+ component: KvEditButton,
7
+ argTypes: {
8
+ visible: {
9
+ control: 'boolean',
10
+ defaultValue: false,
11
+ },
12
+ title: {
13
+ control: 'text',
14
+ defaultValue: 'Edit',
15
+ },
16
+ variant: {
17
+ control: 'select',
18
+ options: ['lightbox', 'alert'],
19
+ defaultValue: 'lightbox',
20
+ },
21
+ preventClose: {
22
+ control: 'boolean',
23
+ defaultValue: false,
24
+ },
25
+ },
26
+ args: {
27
+ visible: false,
28
+ title: '',
29
+ variant: 'lightbox',
30
+ preventClose: false,
31
+ },
32
+ };
33
+
34
+ const DefaultTemplate = (args, { argTypes }) => ({
35
+ props: Object.keys(argTypes),
36
+ components: { KvEditButton, KvButton },
37
+ template: `
38
+ <div>
39
+ <kv-edit-button v-bind="$props">
40
+ <template>This is the content that will appear inside the lightbox when the edit button is clicked.</template>
41
+ <template #header>
42
+ <h3>
43
+ Custom Header
44
+ </h3>
45
+ </template>
46
+ <template #controls="{ hideLightbox }">
47
+ <kv-button variant="ghost" @click="hideLightbox" ref="dontDoItRef">Cancel</kv-button>
48
+ <kv-button variant="danger" @click="hideLightbox" ref="doItRef">Delete</kv-button>
49
+ </template>
50
+ </kv-edit-button>
51
+ </div>
52
+ `,
53
+ data() {
54
+ return {
55
+ isLightboxVisible: args.visible,
56
+ };
57
+ },
58
+ });
59
+
60
+ export const EditButtonWithLightbox = DefaultTemplate.bind({});
61
+ EditButtonWithLightbox.args = {
62
+ title: 'Edit Item Details',
63
+ };
@@ -0,0 +1,52 @@
1
+ import KvLineGraph from '../KvLineGraph.vue';
2
+
3
+ export default {
4
+ title: 'KvLineGraph',
5
+ component: KvLineGraph,
6
+ };
7
+
8
+ const Template = (_args, { argTypes }) => ({
9
+ props: Object.keys(argTypes),
10
+ components: {
11
+ KvLineGraph,
12
+ },
13
+ template: `
14
+ <div style="height: 500px; width: 500px;">
15
+ <kv-line-graph :points="points" :axis-label="axisLabel" />
16
+ </div>
17
+ `,
18
+ });
19
+
20
+ const points = [
21
+ { value: 10 },
22
+ { value: 20 },
23
+ { value: 50 },
24
+ { value: 60 },
25
+ { value: 80 },
26
+ ];
27
+
28
+ const pointsWithLabels = [
29
+ { value: 10, label: '2014' },
30
+ { value: 20, label: '2015' },
31
+ { value: 50, label: '2016' },
32
+ { value: 60, label: '2017' },
33
+ { value: 80, label: '2018' },
34
+ ];
35
+
36
+ export const Default = Template.bind();
37
+ Default.args = { points };
38
+
39
+ export const AxisLabel = Template.bind();
40
+ AxisLabel.args = {
41
+ points,
42
+ axisLabel: 'People supported over time',
43
+ };
44
+
45
+ export const ValueLabels = Template.bind();
46
+ ValueLabels.args = { points: pointsWithLabels };
47
+
48
+ export const AllLabels = Template.bind();
49
+ AllLabels.args = {
50
+ points: pointsWithLabels,
51
+ axisLabel: 'People supported over time',
52
+ };