@dhasdk/simple-ui 1.0.7 → 1.0.8
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/.babelrc +12 -0
- package/.storybook/main.ts +35 -0
- package/.storybook/preview.ts +4 -0
- package/BAKpostcss.config.jsBAK +15 -0
- package/BAKtailwind.config.mjsBAK +99 -0
- package/README.md +464 -16
- package/coverage/storybook/coverage-storybook.json +32411 -0
- package/coverage/storybook/lcov-report/Accordion.tsx.html +805 -0
- package/coverage/storybook/lcov-report/Badge.tsx.html +346 -0
- package/coverage/storybook/lcov-report/Breadcrumbs.tsx.html +742 -0
- package/coverage/storybook/lcov-report/Button.tsx.html +448 -0
- package/coverage/storybook/lcov-report/ButtonGroup.tsx.html +403 -0
- package/coverage/storybook/lcov-report/Card.tsx.html +292 -0
- package/coverage/storybook/lcov-report/CharacterCounter.tsx.html +253 -0
- package/coverage/storybook/lcov-report/CheckBox.tsx.html +1555 -0
- package/coverage/storybook/lcov-report/DatePicker.tsx.html +826 -0
- package/coverage/storybook/lcov-report/Input.tsx.html +1012 -0
- package/coverage/storybook/lcov-report/List.tsx.html +364 -0
- package/coverage/storybook/lcov-report/Modal.tsx.html +745 -0
- package/coverage/storybook/lcov-report/Pill.tsx.html +358 -0
- package/coverage/storybook/lcov-report/Search.tsx.html +997 -0
- package/coverage/storybook/lcov-report/SearchContent.tsx.html +235 -0
- package/coverage/storybook/lcov-report/SectionHeader.tsx.html +358 -0
- package/coverage/storybook/lcov-report/Select.tsx.html +1012 -0
- package/coverage/storybook/lcov-report/Shield.tsx.html +802 -0
- package/coverage/storybook/lcov-report/SideBarNav.tsx.html +490 -0
- package/coverage/storybook/lcov-report/Skeleton.tsx.html +394 -0
- package/coverage/storybook/lcov-report/Slider.tsx.html +385 -0
- package/coverage/storybook/lcov-report/Status.tsx.html +322 -0
- package/coverage/storybook/lcov-report/Tabs.tsx.html +610 -0
- package/coverage/storybook/lcov-report/Toggle.tsx.html +373 -0
- package/coverage/storybook/lcov-report/Tooltip.tsx.html +496 -0
- package/coverage/storybook/lcov-report/base.css +224 -0
- package/coverage/storybook/lcov-report/block-navigation.js +87 -0
- package/coverage/storybook/lcov-report/favicon.png +0 -0
- package/coverage/storybook/lcov-report/index.html +476 -0
- package/coverage/storybook/lcov-report/prettify.css +1 -0
- package/coverage/storybook/lcov-report/prettify.js +2 -0
- package/coverage/storybook/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/storybook/lcov-report/sorter.js +196 -0
- package/coverage/storybook/lcov.info +2312 -0
- package/dist/README.md +1815 -0
- package/eslint.config.mjs +13 -0
- package/package.json +6 -7
- package/project.json +11 -0
- package/src/assets/img/Frame.svg +5 -0
- package/src/assets/img/backArrowRight.svg +10 -0
- package/src/assets/img/bc-separator.png +0 -0
- package/src/assets/img/calendar.png +0 -0
- package/src/assets/img/calendar.svg +4 -0
- package/src/assets/img/check.svg +5 -0
- package/src/assets/img/check_box.svg +10 -0
- package/src/assets/img/check_box_empty.svg +10 -0
- package/src/assets/img/check_box_fill.svg +10 -0
- package/src/assets/img/check_box_fill_empty.svg +10 -0
- package/src/assets/img/chevron-down-white.svg +2 -0
- package/src/assets/img/chevron-down.svg +2 -0
- package/src/assets/img/chevron-left.svg +1 -0
- package/src/assets/img/chevron-right-light.svg +4 -0
- package/src/assets/img/chevron-right.svg +3 -0
- package/src/assets/img/chevron-up-white.svg +1 -0
- package/src/assets/img/chevron-up.svg +1 -0
- package/src/assets/img/clock.svg +6 -0
- package/src/assets/img/close.svg +1 -0
- package/src/assets/img/close2.svg +6 -0
- package/src/assets/img/closeModal.svg +10 -0
- package/src/assets/img/close_icon_dark.svg +10 -0
- package/src/assets/img/close_small.svg +3 -0
- package/src/assets/img/emergency_home.svg +10 -0
- package/src/assets/img/first-aid-kit.svg +7 -0
- package/src/assets/img/heartbeat.svg +4 -0
- package/src/assets/img/home-gray.svg +3 -0
- package/src/assets/img/home.svg +3 -0
- package/src/assets/img/hospital.jpg +0 -0
- package/src/assets/img/indeterminate_check_box.svg +10 -0
- package/src/assets/img/indeterminate_check_box_fill.svg +10 -0
- package/src/assets/img/info_24_ 1d4ed8.svg +3 -0
- package/src/assets/img/info_24_ 2c6441.svg +3 -0
- package/src/assets/img/marker_check_by_default.svg +10 -0
- package/src/assets/img/marker_check_by_default_fill.svg +10 -0
- package/src/assets/img/minus-accordion.svg +5 -0
- package/src/assets/img/minus.svg +3 -0
- package/src/assets/img/open.svg +1 -0
- package/src/assets/img/pill-white.svg +7 -0
- package/src/assets/img/pill.svg +5 -0
- package/src/assets/img/plus-accordion.svg +5 -0
- package/src/assets/img/plus.svg +4 -0
- package/src/assets/img/prescription.svg +6 -0
- package/src/assets/img/search.svg +10 -0
- package/src/assets/img/search_icon_light.svg +10 -0
- package/src/assets/img/separator.svg +3 -0
- package/src/assets/img/stethoscope-white.svg +8 -0
- package/src/assets/img/stethoscope.svg +8 -0
- package/src/assets/img/thumb_up.svg +10 -0
- package/src/assets/img/vector.svg +3 -0
- package/src/assets/img/warning-badge-disabled.svg +11 -0
- package/src/assets/img/warning-badge-green.svg +11 -0
- package/src/assets/img/warning-badge-red.svg +11 -0
- package/src/assets/img/warning-badge-yellow.svg +11 -0
- package/src/assets/img/warning.svg +10 -0
- package/src/global.d.ts +13 -0
- package/{index.d.ts → src/index.ts} +13 -5
- package/src/lib/Accordian--Accordian.stories.tsx +312 -0
- package/src/lib/Accordion.spec.tsx +384 -0
- package/src/lib/Accordion.tsx +240 -0
- package/src/lib/AppointmentPicker.spec.tsx +138 -0
- package/src/lib/AppointmentPicker.tsx +97 -0
- package/src/lib/Badge--Badge.stories.tsx +60 -0
- package/src/lib/Badge.spec.tsx +70 -0
- package/src/lib/Badge.tsx +87 -0
- package/src/lib/Breadcrumbs-Breadcrumbs.stories.tsx +114 -0
- package/src/lib/Breadcrumbs.spec.tsx +218 -0
- package/src/lib/Breadcrumbs.tsx +219 -0
- package/src/lib/Button--Button.stories.tsx +220 -0
- package/src/lib/Button.spec.tsx +241 -0
- package/src/lib/Button.tsx +121 -0
- package/src/lib/ButtonGroup--ButtonGroup.stories.tsx +129 -0
- package/src/lib/ButtonGroup.spec.tsx +89 -0
- package/src/lib/ButtonGroup.tsx +107 -0
- package/src/lib/Card--Card.stories.tsx +113 -0
- package/src/lib/Card.spec.tsx +112 -0
- package/src/lib/Card.tsx +69 -0
- package/src/lib/CharacterCounter--CharacterCounter.stories.tsx +169 -0
- package/src/lib/CharacterCounter.spec.tsx +123 -0
- package/src/lib/CharacterCounter.tsx +56 -0
- package/src/lib/CheckBox--CheckBox.stories.tsx +107 -0
- package/src/lib/CheckBox.spec.tsx +412 -0
- package/src/lib/CheckBox.tsx +491 -0
- package/src/lib/DatePicker--DatePicker.stories.tsx +228 -0
- package/src/lib/DatePicker.spec.tsx +424 -0
- package/src/lib/DatePicker.tsx +247 -0
- package/src/lib/Input--Input.stories.tsx +449 -0
- package/src/lib/Input.spec.tsx +281 -0
- package/src/lib/Input.tsx +309 -0
- package/src/lib/List--List.stories.tsx +157 -0
- package/src/lib/List.spec.tsx +211 -0
- package/src/lib/List.tsx +93 -0
- package/src/lib/Modal--Modal.stories.tsx +454 -0
- package/src/lib/Modal.spec.tsx +202 -0
- package/src/lib/Modal.tsx +220 -0
- package/src/lib/Pill--Pill.stories.tsx +98 -0
- package/src/lib/Pill.spec.tsx +103 -0
- package/src/lib/Pill.tsx +91 -0
- package/src/lib/ProgressBar.spec.tsx +106 -0
- package/src/lib/ProgressBar.tsx +112 -0
- package/src/lib/RadioGroup.spec.tsx +84 -0
- package/src/lib/RadioGroup.tsx +74 -0
- package/src/lib/RadioIcon.tsx +13 -0
- package/src/lib/Search--Search.stories.tsx +67 -0
- package/src/lib/Search.spec.tsx +182 -0
- package/src/lib/Search.tsx +304 -0
- package/src/lib/SearchContent.tsx +51 -0
- package/src/lib/SectionHeader--SectionHeader.stories.tsx +98 -0
- package/src/lib/SectionHeader.spec.tsx +60 -0
- package/src/lib/SectionHeader.tsx +91 -0
- package/src/lib/Select--Select.stories.tsx +387 -0
- package/src/lib/Select.spec.tsx +493 -0
- package/src/lib/Select.tsx +311 -0
- package/src/lib/Shield--Shield.stories.tsx +196 -0
- package/src/lib/Shield.spec.tsx +275 -0
- package/src/lib/Shield.tsx +239 -0
- package/src/lib/SideBarNav--SideBarNav.stories.tsx +136 -0
- package/src/lib/SideBarNav.spec.tsx +178 -0
- package/src/lib/SideBarNav.tsx +135 -0
- package/src/lib/Skeleton--Skeleton.stories.tsx +77 -0
- package/src/lib/Skeleton.module.css +16 -0
- package/src/lib/Skeleton.spec.tsx +83 -0
- package/src/lib/Skeleton.tsx +103 -0
- package/src/lib/SkipLink.spec.tsx +76 -0
- package/src/lib/SkipLink.tsx +48 -0
- package/src/lib/Slider--Slider.stories.tsx +108 -0
- package/src/lib/Slider.module.css +109 -0
- package/src/lib/Slider.spec.tsx +67 -0
- package/src/lib/Slider.tsx +101 -0
- package/src/lib/Status--Status.stories.tsx +93 -0
- package/src/lib/Status.spec.tsx +118 -0
- package/src/lib/Status.tsx +79 -0
- package/src/lib/Tabs--Tabs.stories.tsx +294 -0
- package/src/lib/Tabs.spec.tsx +249 -0
- package/src/lib/Tabs.tsx +188 -0
- package/src/lib/Tester.spec.tsx +17 -0
- package/src/lib/Toggle--Toggle.stories.tsx +162 -0
- package/src/lib/Toggle.spec.tsx +122 -0
- package/src/lib/Toggle.tsx +96 -0
- package/src/lib/Tooltip--Tooltip.stories.tsx +315 -0
- package/src/lib/Tooltip.spec.tsx +307 -0
- package/src/lib/Tooltip.tsx +137 -0
- package/src/lib/bak-simple-ui.stories.tsx-bak +24 -0
- package/src/styles.css +190 -0
- package/tsconfig.json +25 -0
- package/tsconfig.lib.json +42 -0
- package/tsconfig.spec.json +29 -0
- package/tsconfig.storybook.json +36 -0
- package/vite.config.mts +87 -0
- package/vitest.setup.ts +12 -0
- package/index.css +0 -1
- package/index.js +0 -35
- package/index.mjs +0 -4981
- package/lib/Accordion.d.ts +0 -36
- package/lib/AppointmentPicker.d.ts +0 -21
- package/lib/Badge.d.ts +0 -11
- package/lib/Breadcrumbs.d.ts +0 -13
- package/lib/Button.d.ts +0 -15
- package/lib/ButtonGroup.d.ts +0 -8
- package/lib/Card.d.ts +0 -11
- package/lib/CharacterCounter.d.ts +0 -11
- package/lib/CheckBox.d.ts +0 -30
- package/lib/DatePicker.d.ts +0 -7
- package/lib/Input.d.ts +0 -16
- package/lib/List.d.ts +0 -22
- package/lib/Modal.d.ts +0 -18
- package/lib/Pill.d.ts +0 -13
- package/lib/ProgressBar.d.ts +0 -19
- package/lib/RadioGroup.d.ts +0 -15
- package/lib/Search.d.ts +0 -26
- package/lib/SearchContent.d.ts +0 -6
- package/lib/SectionHeader.d.ts +0 -18
- package/lib/Select.d.ts +0 -19
- package/lib/Shield.d.ts +0 -12
- package/lib/SideBarNav.d.ts +0 -21
- package/lib/Skeleton.d.ts +0 -15
- package/lib/SkipLink.d.ts +0 -22
- package/lib/Slider.d.ts +0 -14
- package/lib/Status.d.ts +0 -10
- package/lib/Tabs.d.ts +0 -23
- package/lib/Toggle.d.ts +0 -11
- package/lib/Tooltip.d.ts +0 -14
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { cloneElement, HTMLAttributes, ReactElement, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The props for the {@link SkipLink} component
|
|
5
|
+
* @property className - The class name for the SkipLink component, defaults to 'skip-link'
|
|
6
|
+
* @property children - The child element of the SkipLink component
|
|
7
|
+
* @property skipTo - The css query aiding the selection of the container (main, section etc) we want to scroll to;
|
|
8
|
+
*/
|
|
9
|
+
export interface SkipLinkProps {
|
|
10
|
+
className?: string;
|
|
11
|
+
skipTo: string;
|
|
12
|
+
children: ReactElement<HTMLAttributes<HTMLElement>>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A Skiplink component that allows keyboard users to skip to the main content of the page.
|
|
17
|
+
* See {@link SkipLinkProps} for more information.
|
|
18
|
+
*/
|
|
19
|
+
export const SkipLink = ({ className, children, skipTo }: SkipLinkProps) => {
|
|
20
|
+
let timerId: NodeJS.Timeout | null = null;
|
|
21
|
+
|
|
22
|
+
const onClick = (event: React.SyntheticEvent) => {
|
|
23
|
+
event.preventDefault();
|
|
24
|
+
|
|
25
|
+
const container: HTMLElement | null = document.querySelector(skipTo);
|
|
26
|
+
|
|
27
|
+
if (container) {
|
|
28
|
+
container.tabIndex = -1;
|
|
29
|
+
container.focus();
|
|
30
|
+
timerId = setTimeout(() => container.removeAttribute('tabindex'), 1000);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
return () => {
|
|
36
|
+
if (timerId) {
|
|
37
|
+
clearTimeout(timerId);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}, [timerId]);
|
|
41
|
+
|
|
42
|
+
return cloneElement(children, { onClick, className: className });
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
SkipLink.defaultProps = {
|
|
46
|
+
className: 'skip-link',
|
|
47
|
+
skipTo: 'main:first-of-type',
|
|
48
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Meta, StoryContext, StoryFn } from '@storybook/react';
|
|
2
|
+
import { Slider, SliderProps } from './Slider';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { within, expect, waitFor } from 'storybook/test';
|
|
5
|
+
|
|
6
|
+
// Meta object - defines basic storybook options for this story
|
|
7
|
+
export default {
|
|
8
|
+
title: 'Components/Slider',
|
|
9
|
+
component: Slider,
|
|
10
|
+
argTypes: {
|
|
11
|
+
variant: {
|
|
12
|
+
control: 'select',
|
|
13
|
+
options: ['default', 'filled', 'outline', 'transparent']
|
|
14
|
+
},
|
|
15
|
+
size: {
|
|
16
|
+
control: 'select',
|
|
17
|
+
options: ['default', 'sm', 'lg', 'icon'],
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
args: {
|
|
21
|
+
disabled: false, // set default argument values
|
|
22
|
+
// label: 'Button', // set default argument values
|
|
23
|
+
},
|
|
24
|
+
parameters: {
|
|
25
|
+
layout: 'padded', // options are 'centered', 'fullscreen', and 'padded' (default value)
|
|
26
|
+
backgrounds: {
|
|
27
|
+
default: 'white',
|
|
28
|
+
values: [
|
|
29
|
+
{ name: 'white', value: '#ffffff' },
|
|
30
|
+
{ name: 'medium', value: '#b5bbb7' },
|
|
31
|
+
{ name: 'dark', value: '#000' },
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
} as Meta<SliderProps>;
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
export const Default: StoryFn = () => {
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Slider />
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
export const ValueCallback: StoryFn = () => {
|
|
49
|
+
const [value, setValue] = useState<number>();
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<>
|
|
53
|
+
<Slider startValue={value} changeHandler={setValue} />
|
|
54
|
+
<div>value: {value}</div>
|
|
55
|
+
</>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
export const SliderLabel: StoryFn = () => {
|
|
61
|
+
const [value, setValue] = useState<number>();
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<>
|
|
65
|
+
<Slider
|
|
66
|
+
startValue={value}
|
|
67
|
+
changeHandler={setValue}
|
|
68
|
+
id='1'
|
|
69
|
+
label='Volume Level'
|
|
70
|
+
classNameLabel='font-bold text-blue-900'
|
|
71
|
+
/>
|
|
72
|
+
<div>value: {value}</div>
|
|
73
|
+
</>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
export const NonDefaultValues: StoryFn = () => {
|
|
80
|
+
const [value, setValue] = useState<number>(15);
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<>
|
|
84
|
+
<Slider
|
|
85
|
+
// className='block'
|
|
86
|
+
startValue={value}
|
|
87
|
+
changeHandler={setValue}
|
|
88
|
+
id='1'
|
|
89
|
+
label='Choose a value from 10 ... 25'
|
|
90
|
+
min={10}
|
|
91
|
+
max={25}
|
|
92
|
+
classNameLabel='font-bold text-green-900'
|
|
93
|
+
/>
|
|
94
|
+
<div>value: {value}</div>
|
|
95
|
+
</>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
// // Define "Alternate Classes" story
|
|
102
|
+
// export const AlternateClasses = {
|
|
103
|
+
// args: {
|
|
104
|
+
// children: 'Custom Classes',
|
|
105
|
+
// onClick: () => console.log('Clicked!'),
|
|
106
|
+
// className: 'border-8 border-black text-white bg-orange-500',
|
|
107
|
+
// }
|
|
108
|
+
// };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/*
|
|
2
|
+
-webkit ... chrome, safari, opera, and edge
|
|
3
|
+
-moz ...... firefox
|
|
4
|
+
|
|
5
|
+
Target track w/ these psuedo elements
|
|
6
|
+
::-webkit-slider-runnable-track
|
|
7
|
+
::-moz-range-track
|
|
8
|
+
|
|
9
|
+
Target thumb w/ these psuedo elements
|
|
10
|
+
::-webkit-slider-thumb (fix at minimum thumb offset error)
|
|
11
|
+
::-moz-range-thumb (fix at minimum defaults for border and border-radius)
|
|
12
|
+
|
|
13
|
+
Must also apply focus styles to comply w/ a11y
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
required properties for track are height & background
|
|
17
|
+
- border-radius can be used to round out
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/* test class - can remove */
|
|
21
|
+
.testP {
|
|
22
|
+
color: green;
|
|
23
|
+
font-weight: bold;
|
|
24
|
+
border: 1px solid blue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* test class */
|
|
28
|
+
.slider {
|
|
29
|
+
display: block;
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Will automatically reset browser-defined styles on input-->range */
|
|
34
|
+
input[type="range"] {
|
|
35
|
+
-webkit-appearance: none;
|
|
36
|
+
appearance: none;
|
|
37
|
+
background: transparent;
|
|
38
|
+
cursor: pointer;
|
|
39
|
+
width: 50%;
|
|
40
|
+
margin-bottom: 6px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
input[type="range"]:focus {
|
|
44
|
+
outline: none;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* CHROME, SAFARI, and EDGE styles ---------------------- */
|
|
48
|
+
/* Note: :focus bug in safari re setting values on thumb - it doesn't */
|
|
49
|
+
|
|
50
|
+
/* Track */
|
|
51
|
+
input[type="range"]::-webkit-slider-runnable-track {
|
|
52
|
+
background: #053a5f;
|
|
53
|
+
height: 0.5rem;
|
|
54
|
+
border-radius: 4px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Thumb */
|
|
58
|
+
input[type="range"]::-webkit-slider-thumb {
|
|
59
|
+
-webkit-appearance: none; /* similar purpose as usage above */
|
|
60
|
+
/* to center, apply margin-top=(track height px / 2) - (thumb height px / 2) */
|
|
61
|
+
/* display: inline; */
|
|
62
|
+
background-color: lightblue;
|
|
63
|
+
border: 2px solid lightskyblue;
|
|
64
|
+
width: 1.25em;
|
|
65
|
+
height: 1.25em;
|
|
66
|
+
border-radius: 50%;
|
|
67
|
+
margin-top: -0.4em; /* centers thumb on track */
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* set focus values */
|
|
71
|
+
input[type="range"]:focus::-webkit-slider-thumb {
|
|
72
|
+
border: 1px solid blue;
|
|
73
|
+
outline: 2px solid blue;
|
|
74
|
+
outline-offset: 0.1rem;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* This helps alleviate focus issue w/ Safari */
|
|
78
|
+
input[type="range"]:active::-webkit-slider-thumb {
|
|
79
|
+
border: 1px solid blue;
|
|
80
|
+
outline: 2px solid blue;
|
|
81
|
+
outline-offset: 0.1rem;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
/* FIREFOX styles --------------------------------------- */
|
|
87
|
+
|
|
88
|
+
/* Track */
|
|
89
|
+
input[type="range"]::-moz-range-track {
|
|
90
|
+
background: #053a5f;
|
|
91
|
+
height: 0.5rem;
|
|
92
|
+
border-radius: 4px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Thumb - requires border and border-radius fixes */
|
|
96
|
+
input[type="range"]::-moz-range-thumb {
|
|
97
|
+
background-color: lightblue;
|
|
98
|
+
border: 2px solid lightskyblue; /* border: none to just remove default */
|
|
99
|
+
width: 1.25em;
|
|
100
|
+
height: 1.25em;
|
|
101
|
+
border-radius: 50%; /* border-radius: 0 to just remove default */
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* set focus values */
|
|
105
|
+
input[type="range"]:focus::-moz-range-thumb {
|
|
106
|
+
border: 1px solid blue;
|
|
107
|
+
outline: 2px solid blue;
|
|
108
|
+
outline-offset: 0.1rem;
|
|
109
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import React, { createRef } from 'react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
4
|
+
import { axe } from 'vitest-axe';
|
|
5
|
+
import { Slider } from './Slider';
|
|
6
|
+
|
|
7
|
+
describe('Slider Component', () => {
|
|
8
|
+
it('renders an input of type range', () => {
|
|
9
|
+
render(<Slider />);
|
|
10
|
+
const slider = screen.getByRole('slider');
|
|
11
|
+
expect(slider).toBeInTheDocument();
|
|
12
|
+
expect(slider).toHaveAttribute('type', 'range');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('sets default value to startValue prop', () => {
|
|
16
|
+
render(<Slider startValue={30} />);
|
|
17
|
+
const slider = screen.getByRole('slider');
|
|
18
|
+
expect(slider).toHaveValue('30');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('updates value and calls changeHandler on change', () => {
|
|
22
|
+
const handleChange = vi.fn();
|
|
23
|
+
render(<Slider startValue={10} changeHandler={handleChange} />);
|
|
24
|
+
const slider = screen.getByRole('slider');
|
|
25
|
+
|
|
26
|
+
fireEvent.change(slider, { target: { value: '70' } });
|
|
27
|
+
|
|
28
|
+
expect(slider).toHaveValue('70');
|
|
29
|
+
expect(handleChange).toHaveBeenCalledWith(70);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('forwards ref to the input element', () => {
|
|
33
|
+
const ref = createRef<HTMLInputElement>();
|
|
34
|
+
render(<Slider ref={ref} />);
|
|
35
|
+
expect(ref.current).toBeInstanceOf(HTMLInputElement);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('merges custom className with default CSS module class', () => {
|
|
39
|
+
render(<Slider className="custom-slider" />);
|
|
40
|
+
const slider = screen.getByRole('slider');
|
|
41
|
+
expect(slider).toHaveClass('custom-slider');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('renders a label when id and label props are provided', () => {
|
|
45
|
+
render(<Slider id="volume-slider" label="Volume" />);
|
|
46
|
+
const label = screen.getByText('Volume');
|
|
47
|
+
const slider = screen.getByRole('slider');
|
|
48
|
+
|
|
49
|
+
expect(label).toBeInTheDocument();
|
|
50
|
+
expect(label).toHaveAttribute('for', 'volume-slider');
|
|
51
|
+
expect(slider).toHaveAttribute('id', 'volume-slider');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('does not render a label when id or label is missing', () => {
|
|
55
|
+
render(<Slider id="only-id" />);
|
|
56
|
+
// No <label> element should be present
|
|
57
|
+
expect(screen.queryByLabelText('only-id')).toBeNull();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('Slider Accessibility Tests', () => {
|
|
62
|
+
it('has no accessibility violations when labeled', async () => {
|
|
63
|
+
const { container } = render(<Slider id="a11y-slider" label="Brightness" />);
|
|
64
|
+
const results = await axe(container);
|
|
65
|
+
expect(results).toHaveNoViolations();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
|
|
2
|
+
// This utilizes the HTML input type="range" element
|
|
3
|
+
|
|
4
|
+
// Dev Notes on input>range
|
|
5
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/range
|
|
6
|
+
// Notes on CSS styling
|
|
7
|
+
// https://www.smashingmagazine.com/2021/12/create-custom-range-input-consistent-browsers/
|
|
8
|
+
//
|
|
9
|
+
// May need an alternate means of showing focus due to safari: https://bugs.webkit.org/show_bug.cgi?id=22261&utm_source=chatgpt.com
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* Two main parts to a Slider - 1) track, and 2) thumb
|
|
13
|
+
*
|
|
14
|
+
* Required to apply baseline styles:
|
|
15
|
+
input[type="range"] {
|
|
16
|
+
-webkit-appearance: none;
|
|
17
|
+
appearance: none;
|
|
18
|
+
background: transparent;
|
|
19
|
+
cursor: pointer;
|
|
20
|
+
width: 15rem;
|
|
21
|
+
}
|
|
22
|
+
*
|
|
23
|
+
*
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { ChangeEvent, forwardRef, InputHTMLAttributes, useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
27
|
+
import { twMerge } from 'tailwind-merge';
|
|
28
|
+
import styles from './Slider.module.css';
|
|
29
|
+
|
|
30
|
+
interface VariantType {
|
|
31
|
+
[key: string]: {css: string, labelCss: string};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const variants: VariantType = {
|
|
35
|
+
default: {
|
|
36
|
+
css: 'w-full block mb-2',
|
|
37
|
+
labelCss: 'block mb-2'
|
|
38
|
+
} ,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
export interface SliderProps extends InputHTMLAttributes<HTMLInputElement> {
|
|
43
|
+
className?: string; // classname to alter div that contains the ButtonGroup
|
|
44
|
+
classNameLabel?: string;
|
|
45
|
+
variant?: string; // unused currently
|
|
46
|
+
startValue?: number; //
|
|
47
|
+
changeHandler?: (value: number) => void; // callback function on value change
|
|
48
|
+
id?: string;
|
|
49
|
+
label?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* id, name, list are common options for slider
|
|
54
|
+
*/
|
|
55
|
+
export const Slider = forwardRef<HTMLInputElement | HTMLLabelElement, SliderProps>(
|
|
56
|
+
({ className, classNameLabel, variant = 'default', startValue = 50,
|
|
57
|
+
changeHandler, id, label, ...props }, ref) => {
|
|
58
|
+
|
|
59
|
+
const [value, setValue] = useState<number>(startValue);
|
|
60
|
+
const localRef = useRef<HTMLInputElement>(null);
|
|
61
|
+
|
|
62
|
+
// expose our local ref to parent
|
|
63
|
+
useImperativeHandle(ref, () => localRef.current as HTMLInputElement);
|
|
64
|
+
|
|
65
|
+
// set starting value for slider component
|
|
66
|
+
useEffect (() => {
|
|
67
|
+
setValue(startValue);
|
|
68
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
const changeFunc = (e: ChangeEvent<HTMLInputElement>) => {
|
|
72
|
+
const curVal = Number(e.target.value);
|
|
73
|
+
setValue(Number(curVal));
|
|
74
|
+
if (changeHandler) {
|
|
75
|
+
changeHandler(curVal);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<>
|
|
81
|
+
{ id && label &&
|
|
82
|
+
<label htmlFor={id}
|
|
83
|
+
className={twMerge(variants[variant].labelCss, classNameLabel)}
|
|
84
|
+
>{label}</label>
|
|
85
|
+
}
|
|
86
|
+
<input
|
|
87
|
+
ref={localRef}
|
|
88
|
+
type='range'
|
|
89
|
+
id={id}
|
|
90
|
+
className={twMerge(styles.slider, className)} // works w/ CSS modules
|
|
91
|
+
value={value}
|
|
92
|
+
onChange={changeFunc}
|
|
93
|
+
{...props}
|
|
94
|
+
/>
|
|
95
|
+
</>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// Chris suggested for debug component name labeling
|
|
101
|
+
Slider.displayName = 'SDK Slider'
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Badge.stories.tsx
|
|
2
|
+
|
|
3
|
+
import { Meta} from '@storybook/react';
|
|
4
|
+
import { Status } from './Status';
|
|
5
|
+
|
|
6
|
+
// Import your images
|
|
7
|
+
import firstAidKit from '../assets/img/first-aid-kit.svg';
|
|
8
|
+
import heartbeat from '../assets/img/heartbeat.svg';
|
|
9
|
+
import pill from '../assets/img/pill.svg';
|
|
10
|
+
import prescription from '../assets/img/prescription.svg';
|
|
11
|
+
import stethoscope from '../assets/img/stethoscope.svg';
|
|
12
|
+
import info from '../assets/img/info_24_ 2c6441.svg';
|
|
13
|
+
import infoBlue from '../assets/img/info_24_ 1d4ed8.svg';
|
|
14
|
+
// https://fonts.google.com/icons?icon.set=Material+Icons&icon.style=Filled
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
title: 'Components/Status',
|
|
18
|
+
component: Status,
|
|
19
|
+
argTypes: {
|
|
20
|
+
variant: {
|
|
21
|
+
control: 'select',
|
|
22
|
+
options: ['default', 'icon', 'media']
|
|
23
|
+
},
|
|
24
|
+
subVariant: {
|
|
25
|
+
control: 'select',
|
|
26
|
+
options: ['default', 'gray', 'red', 'green', 'yellow', 'blue'],
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
parameters: {
|
|
30
|
+
layout: 'centered',
|
|
31
|
+
backgrounds: { default: 'light' },
|
|
32
|
+
},
|
|
33
|
+
} as Meta<typeof Status>;
|
|
34
|
+
|
|
35
|
+
// DefaultBadge story
|
|
36
|
+
// export const DefaultStatus = {
|
|
37
|
+
// args: {
|
|
38
|
+
// variant: 'default',
|
|
39
|
+
// subVariant: 'default',
|
|
40
|
+
// children: 'Badge!',
|
|
41
|
+
// }
|
|
42
|
+
// };
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
export const Available = {
|
|
46
|
+
args: {
|
|
47
|
+
variant: 'available',
|
|
48
|
+
children: 'Badge!',
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const InProgress = {
|
|
53
|
+
args: {
|
|
54
|
+
variant: 'inProgress',
|
|
55
|
+
children: 'Badge!',
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
export const NotAvailable = {
|
|
61
|
+
args: {
|
|
62
|
+
variant: 'notAvailable',
|
|
63
|
+
children: 'Badge!',
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
export const Default = {
|
|
69
|
+
args: {
|
|
70
|
+
// variant: 'notAvailable',
|
|
71
|
+
children: 'Badge!',
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
export const CustomImage = {
|
|
77
|
+
args: {
|
|
78
|
+
// variant: 'notAvailable',
|
|
79
|
+
children: 'information',
|
|
80
|
+
image: <img src={info} className='fill-green-500 stroke-green-500' alt='information' />
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
export const CustomImageStyling = {
|
|
86
|
+
args: {
|
|
87
|
+
// variant: 'notAvailable',
|
|
88
|
+
children: 'information',
|
|
89
|
+
image: <img src={infoBlue} className='fill-green-500 stroke-green-500' alt='information' />,
|
|
90
|
+
className: 'border-blue-700 bg-blue-200',
|
|
91
|
+
classNameChild: 'text-blue-700',
|
|
92
|
+
}
|
|
93
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Status.test.tsx
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
4
|
+
import { axe } from 'vitest-axe';
|
|
5
|
+
import { createRef } from 'react';
|
|
6
|
+
import { Status } from './Status';
|
|
7
|
+
|
|
8
|
+
describe('Status Component', () => {
|
|
9
|
+
it('renders with the default "available" variant and displays the check image', () => {
|
|
10
|
+
render(<Status>Available Status</Status>);
|
|
11
|
+
|
|
12
|
+
// Verify the children text is rendered.
|
|
13
|
+
expect(screen.getByText('Available Status')).toBeInTheDocument();
|
|
14
|
+
|
|
15
|
+
// Verify the default check image is rendered.
|
|
16
|
+
const img = screen.getByRole('img', { name: /check mark/i });
|
|
17
|
+
expect(img).toBeInTheDocument();
|
|
18
|
+
|
|
19
|
+
// Optionally, check that the outer container has a class from the available variant (e.g., bg-green-50)
|
|
20
|
+
const container = screen.getByText('Available Status').parentElement;
|
|
21
|
+
expect(container).toHaveClass('bg-green-50');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('renders the "inProgress" variant and displays the clock image', () => {
|
|
25
|
+
render(<Status variant="inProgress">In Progress Status</Status>);
|
|
26
|
+
|
|
27
|
+
expect(screen.getByText('In Progress Status')).toBeInTheDocument();
|
|
28
|
+
|
|
29
|
+
const img = screen.getByRole('img', { name: /clock/i });
|
|
30
|
+
expect(img).toBeInTheDocument();
|
|
31
|
+
|
|
32
|
+
// Check that the container has a class for the inProgress variant (e.g., bg-yellow-50)
|
|
33
|
+
const container = screen.getByText('In Progress Status').parentElement;
|
|
34
|
+
expect(container).toHaveClass('bg-yellow-50');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('renders the "notAvailable" variant and displays the close image', () => {
|
|
38
|
+
render(<Status variant="notAvailable">Not Available Status</Status>);
|
|
39
|
+
|
|
40
|
+
expect(screen.getByText('Not Available Status')).toBeInTheDocument();
|
|
41
|
+
|
|
42
|
+
const img = screen.getByRole('img', { name: /close/i });
|
|
43
|
+
expect(img).toBeInTheDocument();
|
|
44
|
+
|
|
45
|
+
const container = screen.getByText('Not Available Status').parentElement;
|
|
46
|
+
expect(container).toHaveClass('bg-red-50');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('renders a custom image when the image prop is provided', () => {
|
|
50
|
+
render(
|
|
51
|
+
<Status image={<img src="custom.svg" alt="custom image" />}>
|
|
52
|
+
Custom Image Status
|
|
53
|
+
</Status>
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
expect(screen.getByText('Custom Image Status')).toBeInTheDocument();
|
|
57
|
+
|
|
58
|
+
// Verify the custom image is rendered instead of the default one.
|
|
59
|
+
const customImg = screen.getByRole('img', { name: /custom image/i });
|
|
60
|
+
expect(customImg).toBeInTheDocument();
|
|
61
|
+
expect(customImg).toHaveAttribute('src', 'custom.svg');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('merges additional className props on the container', () => {
|
|
65
|
+
render(<Status className="extra-class">Extra Class Test</Status>);
|
|
66
|
+
const container = screen.getByText('Extra Class Test').parentElement;
|
|
67
|
+
|
|
68
|
+
// The container should include both the default classes and the extra class.
|
|
69
|
+
expect(container).toHaveClass('extra-class');
|
|
70
|
+
expect(container).toHaveClass('bg-green-50'); // default for "available" variant
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('merges additional classNameChild props on the text element', () => {
|
|
74
|
+
render(
|
|
75
|
+
<Status classNameChild="child-extra-class">
|
|
76
|
+
Child Extra Class Test
|
|
77
|
+
</Status>
|
|
78
|
+
);
|
|
79
|
+
const childDiv = screen.getByText('Child Extra Class Test');
|
|
80
|
+
|
|
81
|
+
expect(childDiv).toHaveClass('child-extra-class');
|
|
82
|
+
// Also verify that the default child classes (e.g., text-green-900) are still present.
|
|
83
|
+
expect(childDiv).toHaveClass('text-green-900');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('merges additional classNameImage props on the image container', () => {
|
|
87
|
+
render(
|
|
88
|
+
<Status
|
|
89
|
+
classNameImage="img-extra-class"
|
|
90
|
+
image={<img src="custom.svg" alt="custom image" />}
|
|
91
|
+
>
|
|
92
|
+
Image Extra Class Test
|
|
93
|
+
</Status>
|
|
94
|
+
);
|
|
95
|
+
// The image is rendered inside a wrapping div; get that div.
|
|
96
|
+
const imageContainer = screen.getByRole('img', { name: /custom image/i })
|
|
97
|
+
.parentElement;
|
|
98
|
+
|
|
99
|
+
expect(imageContainer).toHaveClass('img-extra-class');
|
|
100
|
+
// Check that the default image container classes (e.g., w-5) are also present.
|
|
101
|
+
expect(imageContainer).toHaveClass('w-5');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('forwards the ref correctly', () => {
|
|
105
|
+
const ref = createRef<HTMLDivElement>();
|
|
106
|
+
render(<Status ref={ref}>Ref Forwarding Test</Status>);
|
|
107
|
+
|
|
108
|
+
// The ref should be attached to the outer container div.
|
|
109
|
+
expect(ref.current).not.toBeNull();
|
|
110
|
+
expect(ref.current).toBeInstanceOf(HTMLDivElement);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('has no detectable accessibility violations', async () => {
|
|
114
|
+
const { container } = render(<Status>Accessible Status</Status>);
|
|
115
|
+
const results = await axe(container);
|
|
116
|
+
expect(results).toHaveNoViolations();
|
|
117
|
+
});
|
|
118
|
+
});
|