@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.
Files changed (227) hide show
  1. package/.babelrc +12 -0
  2. package/.storybook/main.ts +35 -0
  3. package/.storybook/preview.ts +4 -0
  4. package/BAKpostcss.config.jsBAK +15 -0
  5. package/BAKtailwind.config.mjsBAK +99 -0
  6. package/README.md +464 -16
  7. package/coverage/storybook/coverage-storybook.json +32411 -0
  8. package/coverage/storybook/lcov-report/Accordion.tsx.html +805 -0
  9. package/coverage/storybook/lcov-report/Badge.tsx.html +346 -0
  10. package/coverage/storybook/lcov-report/Breadcrumbs.tsx.html +742 -0
  11. package/coverage/storybook/lcov-report/Button.tsx.html +448 -0
  12. package/coverage/storybook/lcov-report/ButtonGroup.tsx.html +403 -0
  13. package/coverage/storybook/lcov-report/Card.tsx.html +292 -0
  14. package/coverage/storybook/lcov-report/CharacterCounter.tsx.html +253 -0
  15. package/coverage/storybook/lcov-report/CheckBox.tsx.html +1555 -0
  16. package/coverage/storybook/lcov-report/DatePicker.tsx.html +826 -0
  17. package/coverage/storybook/lcov-report/Input.tsx.html +1012 -0
  18. package/coverage/storybook/lcov-report/List.tsx.html +364 -0
  19. package/coverage/storybook/lcov-report/Modal.tsx.html +745 -0
  20. package/coverage/storybook/lcov-report/Pill.tsx.html +358 -0
  21. package/coverage/storybook/lcov-report/Search.tsx.html +997 -0
  22. package/coverage/storybook/lcov-report/SearchContent.tsx.html +235 -0
  23. package/coverage/storybook/lcov-report/SectionHeader.tsx.html +358 -0
  24. package/coverage/storybook/lcov-report/Select.tsx.html +1012 -0
  25. package/coverage/storybook/lcov-report/Shield.tsx.html +802 -0
  26. package/coverage/storybook/lcov-report/SideBarNav.tsx.html +490 -0
  27. package/coverage/storybook/lcov-report/Skeleton.tsx.html +394 -0
  28. package/coverage/storybook/lcov-report/Slider.tsx.html +385 -0
  29. package/coverage/storybook/lcov-report/Status.tsx.html +322 -0
  30. package/coverage/storybook/lcov-report/Tabs.tsx.html +610 -0
  31. package/coverage/storybook/lcov-report/Toggle.tsx.html +373 -0
  32. package/coverage/storybook/lcov-report/Tooltip.tsx.html +496 -0
  33. package/coverage/storybook/lcov-report/base.css +224 -0
  34. package/coverage/storybook/lcov-report/block-navigation.js +87 -0
  35. package/coverage/storybook/lcov-report/favicon.png +0 -0
  36. package/coverage/storybook/lcov-report/index.html +476 -0
  37. package/coverage/storybook/lcov-report/prettify.css +1 -0
  38. package/coverage/storybook/lcov-report/prettify.js +2 -0
  39. package/coverage/storybook/lcov-report/sort-arrow-sprite.png +0 -0
  40. package/coverage/storybook/lcov-report/sorter.js +196 -0
  41. package/coverage/storybook/lcov.info +2312 -0
  42. package/dist/README.md +1815 -0
  43. package/eslint.config.mjs +13 -0
  44. package/package.json +6 -7
  45. package/project.json +11 -0
  46. package/src/assets/img/Frame.svg +5 -0
  47. package/src/assets/img/backArrowRight.svg +10 -0
  48. package/src/assets/img/bc-separator.png +0 -0
  49. package/src/assets/img/calendar.png +0 -0
  50. package/src/assets/img/calendar.svg +4 -0
  51. package/src/assets/img/check.svg +5 -0
  52. package/src/assets/img/check_box.svg +10 -0
  53. package/src/assets/img/check_box_empty.svg +10 -0
  54. package/src/assets/img/check_box_fill.svg +10 -0
  55. package/src/assets/img/check_box_fill_empty.svg +10 -0
  56. package/src/assets/img/chevron-down-white.svg +2 -0
  57. package/src/assets/img/chevron-down.svg +2 -0
  58. package/src/assets/img/chevron-left.svg +1 -0
  59. package/src/assets/img/chevron-right-light.svg +4 -0
  60. package/src/assets/img/chevron-right.svg +3 -0
  61. package/src/assets/img/chevron-up-white.svg +1 -0
  62. package/src/assets/img/chevron-up.svg +1 -0
  63. package/src/assets/img/clock.svg +6 -0
  64. package/src/assets/img/close.svg +1 -0
  65. package/src/assets/img/close2.svg +6 -0
  66. package/src/assets/img/closeModal.svg +10 -0
  67. package/src/assets/img/close_icon_dark.svg +10 -0
  68. package/src/assets/img/close_small.svg +3 -0
  69. package/src/assets/img/emergency_home.svg +10 -0
  70. package/src/assets/img/first-aid-kit.svg +7 -0
  71. package/src/assets/img/heartbeat.svg +4 -0
  72. package/src/assets/img/home-gray.svg +3 -0
  73. package/src/assets/img/home.svg +3 -0
  74. package/src/assets/img/hospital.jpg +0 -0
  75. package/src/assets/img/indeterminate_check_box.svg +10 -0
  76. package/src/assets/img/indeterminate_check_box_fill.svg +10 -0
  77. package/src/assets/img/info_24_ 1d4ed8.svg +3 -0
  78. package/src/assets/img/info_24_ 2c6441.svg +3 -0
  79. package/src/assets/img/marker_check_by_default.svg +10 -0
  80. package/src/assets/img/marker_check_by_default_fill.svg +10 -0
  81. package/src/assets/img/minus-accordion.svg +5 -0
  82. package/src/assets/img/minus.svg +3 -0
  83. package/src/assets/img/open.svg +1 -0
  84. package/src/assets/img/pill-white.svg +7 -0
  85. package/src/assets/img/pill.svg +5 -0
  86. package/src/assets/img/plus-accordion.svg +5 -0
  87. package/src/assets/img/plus.svg +4 -0
  88. package/src/assets/img/prescription.svg +6 -0
  89. package/src/assets/img/search.svg +10 -0
  90. package/src/assets/img/search_icon_light.svg +10 -0
  91. package/src/assets/img/separator.svg +3 -0
  92. package/src/assets/img/stethoscope-white.svg +8 -0
  93. package/src/assets/img/stethoscope.svg +8 -0
  94. package/src/assets/img/thumb_up.svg +10 -0
  95. package/src/assets/img/vector.svg +3 -0
  96. package/src/assets/img/warning-badge-disabled.svg +11 -0
  97. package/src/assets/img/warning-badge-green.svg +11 -0
  98. package/src/assets/img/warning-badge-red.svg +11 -0
  99. package/src/assets/img/warning-badge-yellow.svg +11 -0
  100. package/src/assets/img/warning.svg +10 -0
  101. package/src/global.d.ts +13 -0
  102. package/{index.d.ts → src/index.ts} +13 -5
  103. package/src/lib/Accordian--Accordian.stories.tsx +312 -0
  104. package/src/lib/Accordion.spec.tsx +384 -0
  105. package/src/lib/Accordion.tsx +240 -0
  106. package/src/lib/AppointmentPicker.spec.tsx +138 -0
  107. package/src/lib/AppointmentPicker.tsx +97 -0
  108. package/src/lib/Badge--Badge.stories.tsx +60 -0
  109. package/src/lib/Badge.spec.tsx +70 -0
  110. package/src/lib/Badge.tsx +87 -0
  111. package/src/lib/Breadcrumbs-Breadcrumbs.stories.tsx +114 -0
  112. package/src/lib/Breadcrumbs.spec.tsx +218 -0
  113. package/src/lib/Breadcrumbs.tsx +219 -0
  114. package/src/lib/Button--Button.stories.tsx +220 -0
  115. package/src/lib/Button.spec.tsx +241 -0
  116. package/src/lib/Button.tsx +121 -0
  117. package/src/lib/ButtonGroup--ButtonGroup.stories.tsx +129 -0
  118. package/src/lib/ButtonGroup.spec.tsx +89 -0
  119. package/src/lib/ButtonGroup.tsx +107 -0
  120. package/src/lib/Card--Card.stories.tsx +113 -0
  121. package/src/lib/Card.spec.tsx +112 -0
  122. package/src/lib/Card.tsx +69 -0
  123. package/src/lib/CharacterCounter--CharacterCounter.stories.tsx +169 -0
  124. package/src/lib/CharacterCounter.spec.tsx +123 -0
  125. package/src/lib/CharacterCounter.tsx +56 -0
  126. package/src/lib/CheckBox--CheckBox.stories.tsx +107 -0
  127. package/src/lib/CheckBox.spec.tsx +412 -0
  128. package/src/lib/CheckBox.tsx +491 -0
  129. package/src/lib/DatePicker--DatePicker.stories.tsx +228 -0
  130. package/src/lib/DatePicker.spec.tsx +424 -0
  131. package/src/lib/DatePicker.tsx +247 -0
  132. package/src/lib/Input--Input.stories.tsx +449 -0
  133. package/src/lib/Input.spec.tsx +281 -0
  134. package/src/lib/Input.tsx +309 -0
  135. package/src/lib/List--List.stories.tsx +157 -0
  136. package/src/lib/List.spec.tsx +211 -0
  137. package/src/lib/List.tsx +93 -0
  138. package/src/lib/Modal--Modal.stories.tsx +454 -0
  139. package/src/lib/Modal.spec.tsx +202 -0
  140. package/src/lib/Modal.tsx +220 -0
  141. package/src/lib/Pill--Pill.stories.tsx +98 -0
  142. package/src/lib/Pill.spec.tsx +103 -0
  143. package/src/lib/Pill.tsx +91 -0
  144. package/src/lib/ProgressBar.spec.tsx +106 -0
  145. package/src/lib/ProgressBar.tsx +112 -0
  146. package/src/lib/RadioGroup.spec.tsx +84 -0
  147. package/src/lib/RadioGroup.tsx +74 -0
  148. package/src/lib/RadioIcon.tsx +13 -0
  149. package/src/lib/Search--Search.stories.tsx +67 -0
  150. package/src/lib/Search.spec.tsx +182 -0
  151. package/src/lib/Search.tsx +304 -0
  152. package/src/lib/SearchContent.tsx +51 -0
  153. package/src/lib/SectionHeader--SectionHeader.stories.tsx +98 -0
  154. package/src/lib/SectionHeader.spec.tsx +60 -0
  155. package/src/lib/SectionHeader.tsx +91 -0
  156. package/src/lib/Select--Select.stories.tsx +387 -0
  157. package/src/lib/Select.spec.tsx +493 -0
  158. package/src/lib/Select.tsx +311 -0
  159. package/src/lib/Shield--Shield.stories.tsx +196 -0
  160. package/src/lib/Shield.spec.tsx +275 -0
  161. package/src/lib/Shield.tsx +239 -0
  162. package/src/lib/SideBarNav--SideBarNav.stories.tsx +136 -0
  163. package/src/lib/SideBarNav.spec.tsx +178 -0
  164. package/src/lib/SideBarNav.tsx +135 -0
  165. package/src/lib/Skeleton--Skeleton.stories.tsx +77 -0
  166. package/src/lib/Skeleton.module.css +16 -0
  167. package/src/lib/Skeleton.spec.tsx +83 -0
  168. package/src/lib/Skeleton.tsx +103 -0
  169. package/src/lib/SkipLink.spec.tsx +76 -0
  170. package/src/lib/SkipLink.tsx +48 -0
  171. package/src/lib/Slider--Slider.stories.tsx +108 -0
  172. package/src/lib/Slider.module.css +109 -0
  173. package/src/lib/Slider.spec.tsx +67 -0
  174. package/src/lib/Slider.tsx +101 -0
  175. package/src/lib/Status--Status.stories.tsx +93 -0
  176. package/src/lib/Status.spec.tsx +118 -0
  177. package/src/lib/Status.tsx +79 -0
  178. package/src/lib/Tabs--Tabs.stories.tsx +294 -0
  179. package/src/lib/Tabs.spec.tsx +249 -0
  180. package/src/lib/Tabs.tsx +188 -0
  181. package/src/lib/Tester.spec.tsx +17 -0
  182. package/src/lib/Toggle--Toggle.stories.tsx +162 -0
  183. package/src/lib/Toggle.spec.tsx +122 -0
  184. package/src/lib/Toggle.tsx +96 -0
  185. package/src/lib/Tooltip--Tooltip.stories.tsx +315 -0
  186. package/src/lib/Tooltip.spec.tsx +307 -0
  187. package/src/lib/Tooltip.tsx +137 -0
  188. package/src/lib/bak-simple-ui.stories.tsx-bak +24 -0
  189. package/src/styles.css +190 -0
  190. package/tsconfig.json +25 -0
  191. package/tsconfig.lib.json +42 -0
  192. package/tsconfig.spec.json +29 -0
  193. package/tsconfig.storybook.json +36 -0
  194. package/vite.config.mts +87 -0
  195. package/vitest.setup.ts +12 -0
  196. package/index.css +0 -1
  197. package/index.js +0 -35
  198. package/index.mjs +0 -4981
  199. package/lib/Accordion.d.ts +0 -36
  200. package/lib/AppointmentPicker.d.ts +0 -21
  201. package/lib/Badge.d.ts +0 -11
  202. package/lib/Breadcrumbs.d.ts +0 -13
  203. package/lib/Button.d.ts +0 -15
  204. package/lib/ButtonGroup.d.ts +0 -8
  205. package/lib/Card.d.ts +0 -11
  206. package/lib/CharacterCounter.d.ts +0 -11
  207. package/lib/CheckBox.d.ts +0 -30
  208. package/lib/DatePicker.d.ts +0 -7
  209. package/lib/Input.d.ts +0 -16
  210. package/lib/List.d.ts +0 -22
  211. package/lib/Modal.d.ts +0 -18
  212. package/lib/Pill.d.ts +0 -13
  213. package/lib/ProgressBar.d.ts +0 -19
  214. package/lib/RadioGroup.d.ts +0 -15
  215. package/lib/Search.d.ts +0 -26
  216. package/lib/SearchContent.d.ts +0 -6
  217. package/lib/SectionHeader.d.ts +0 -18
  218. package/lib/Select.d.ts +0 -19
  219. package/lib/Shield.d.ts +0 -12
  220. package/lib/SideBarNav.d.ts +0 -21
  221. package/lib/Skeleton.d.ts +0 -15
  222. package/lib/SkipLink.d.ts +0 -22
  223. package/lib/Slider.d.ts +0 -14
  224. package/lib/Status.d.ts +0 -10
  225. package/lib/Tabs.d.ts +0 -23
  226. package/lib/Toggle.d.ts +0 -11
  227. 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
+ });