@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,304 @@
1
+ import { ChangeEvent, forwardRef, InputHTMLAttributes, KeyboardEvent, useEffect, useRef, useState } from 'react';
2
+ import SearchIconLg from '../assets/img/search_icon_light.svg';
3
+ import SearchIcon from '../assets/img/search.svg';
4
+ import CloseIcon from '../assets/img/close_icon_dark.svg';
5
+ import { twMerge } from 'tailwind-merge';
6
+ // import { useNavigate } from 'react-router-dom';
7
+
8
+ // TODO: UX Question re no results
9
+ // NOTE: UX (Sam) noted that a results page will be required for some cases. We talked, and we will
10
+ // return an object or set a callback that supplies an object of results as they are presented
11
+ // in the type-ahead results list at the point the user clicks the button
12
+ // NOTE: UX (Sam) indicated that the 'X' clears the search content, does NOT shrink it. That should
13
+ // probably be a state var like 'initial = icon', user then clicks to expand
14
+
15
+ // Searchable Data must conform to an array of this interface
16
+ export interface SearchDataItem {
17
+ path: string;
18
+ title?: string;
19
+ content: string;
20
+ }
21
+
22
+ // Interface of search results - can use index to obtain path
23
+ export interface DataSearchResults {
24
+ input: string;
25
+ pages: {
26
+ title?: string;
27
+ path?: string;
28
+ results: {
29
+ characters: string;
30
+ index: number;
31
+ }[]
32
+ }[]
33
+ }
34
+
35
+ export interface SearchProps
36
+ extends InputHTMLAttributes<HTMLInputElement> {
37
+ className?: string;
38
+ searchableData: SearchDataItem[];
39
+ ariaLabel?: string;
40
+ iconLink?: boolean;
41
+ setSearchResults?: (res: DataSearchResults) => void;
42
+ mobileOnly?: boolean;
43
+ }
44
+
45
+ export const Search = forwardRef<HTMLInputElement, SearchProps>(
46
+ (
47
+ {
48
+ className,
49
+ ariaLabel = "Search",
50
+ iconLink = false,
51
+ searchableData,
52
+ setSearchResults,
53
+ mobileOnly = false,
54
+ ...props
55
+ },
56
+ ref
57
+ ) => {
58
+ const inputRef = useRef<HTMLInputElement | null>(null);
59
+ const resultsRef = useRef<HTMLDivElement>(null); // TODO: necessary?
60
+ const [value, setValue] = useState('');
61
+ const [resultsList, setResultsList] = useState<DataSearchResults>();
62
+ const [iconOnly, setIconOnly] = useState<boolean>(iconLink);
63
+ // const [groupedResults, setGroupedResults] = useState();
64
+ // const [navigatePath, setNavigatePath] = useState<string>();
65
+ // const navigate = useNavigate();
66
+
67
+ // useEffect (() => {
68
+ // if (navigatePath) {
69
+ // navigate(navigatePath);
70
+ // }
71
+ // }, [navigate, navigatePath]);
72
+
73
+ const handleEnter = (e: KeyboardEvent<HTMLInputElement>) => {
74
+ // if returning results list:
75
+ if (e.key === 'Enter') {
76
+ if (setSearchResults) {
77
+ // we are returning results - do that
78
+ handleSearchClick();
79
+ } else {
80
+ // execute first result of real-time list
81
+ if (resultsList && resultsList.pages[0].path) {
82
+ // console.log('resultsList[0]: ' + resultsList.pages[0].path);
83
+ window.location.href=resultsList.pages[0].path;
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ const selectX = () => {
90
+ setValue(''); // if xClears, onClick clear content
91
+ setResultsList(undefined);
92
+ }
93
+
94
+ useEffect (() => {
95
+ setIconOnly(iconLink);
96
+ }, [iconLink]);
97
+
98
+ // Search Function string input
99
+ // returns 15 characters following searched term (add term plus return characters string)
100
+ // as well as array index (from which we can retrieve the path value)
101
+ function searchFilter(searchText: string): DataSearchResults {
102
+ const lower = searchText.toLowerCase();
103
+ const pages: DataSearchResults["pages"] = [];
104
+
105
+ searchableData.forEach((page, pageIndex) => {
106
+ const pageResults: { characters: string; index: number }[] = [];
107
+ let start = page.content.toLowerCase().indexOf(lower);
108
+
109
+ while (start !== -1) {
110
+ const snippet = page.content.substring(
111
+ start + lower.length,
112
+ start + lower.length + 15
113
+ );
114
+ pageResults.push({ characters: snippet, index: pageIndex });
115
+ start = page.content.toLowerCase().indexOf(lower, start + 1);
116
+ }
117
+
118
+ // only emit a page entry if we found at least one match
119
+ if (pageResults.length) {
120
+ pages.push({
121
+ title: page.title,
122
+ path: page.path,
123
+ results: pageResults,
124
+ });
125
+ }
126
+ });
127
+
128
+ return { input: searchText, pages };
129
+ }
130
+
131
+ // Call searchFilter() function w/ input data if appropriate
132
+ const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
133
+ const newValue = event.target.value;
134
+ setValue(newValue);
135
+
136
+ if (!newValue) {
137
+ // clear out search results when input is empty
138
+ // setSearchResults?.({ input: "", pages: [] });
139
+ setResultsList(undefined);
140
+ return;
141
+ }
142
+
143
+ const results = searchFilter(newValue);
144
+ // setSearchResults?.(results); // call-back for use in storybook (temp?)
145
+ setResultsList(results); // local results
146
+ };
147
+
148
+
149
+ const handleSearchClick = () => {
150
+ const newValue = inputRef.current?.value ?? '';
151
+
152
+
153
+ if (setSearchResults) {
154
+ if (newValue.length > 0) {
155
+ const results = searchFilter(newValue);
156
+ setSearchResults?.(results);
157
+ } else {
158
+ setSearchResults({input: '', pages: [{results: []}]});
159
+ }
160
+ }
161
+ };
162
+
163
+ // mobile - w: 309 h: 48 border: 0.5
164
+ // input h-32px p: 8x8
165
+ // tablet - w: 334 h: 50 border: 0.5
166
+ // input h-34px p: 8x8
167
+ // desk - w: 350 h: 56
168
+ // input h: 56 p: 0
169
+
170
+ const inputClasses = `grow appearance-none h-8 md:h-[32px] lg:h-14 py-2 lg:ps-[19px] rounded-tl rounded-bl
171
+ rounded-br-none rounded-tr-none lg:border border-[#07192d]
172
+ font-['Arial'] bg-white text-[#333234] text-base lg:text-lg
173
+ hover:border-[#5992fa] focus:border-[#5992fa] outline-hidden `;
174
+
175
+ // TODO: Border Color for FOCUS
176
+ return (
177
+ iconOnly ?
178
+ // Display solo search icon button to expand search component
179
+ <span className='w-[52px] h-[50px] p-2 rounded-sm border border-[#6a686b]
180
+ inline-flex flex-col justify-center items-center bg-white'>
181
+ <button className="appearance-none p-0 m-0 border-0 flex items-center justify-center align-middle"
182
+ onClick={() => setIconOnly(false)} >
183
+ <img className='' src={SearchIcon} alt='search icon' />
184
+ </button>
185
+ </span>
186
+ :
187
+
188
+ // Container including input + output list box
189
+ <span className={`h-12 md:h-[50px] lg:h-14 flex items-center relative ${iconOnly && 'hidden'}`}>
190
+
191
+ {/* container for input + icon(s), displays border up to md only */}
192
+ <span className={`
193
+ border-[#6a686b] w-[309px] md:w-[334px] lg:w-[350px]
194
+ inline-flex bg-white items-center
195
+ ${resultsList ?
196
+ ' border-t border-l border-r rounded-tl rounded-tr lg:border-none ' : // results, round top corners only
197
+ ' border lg:border-0 rounded-sm ' // no results, box only rounded
198
+ }
199
+ `}>
200
+ {/* padding 12px */}
201
+ {/* left search icon (small & medium only */}
202
+ <button onClick={handleSearchClick}>
203
+ <span className='lg:hidden rounded-tl px-2 rounded-bl '>
204
+ <img className='' src={SearchIcon} alt='search icon' />
205
+ </span>
206
+ </button>
207
+ <input
208
+ className={twMerge(inputClasses, className)}
209
+ aria-label={ariaLabel}
210
+ value={value}
211
+ onChange={handleInputChange}
212
+ placeholder='Search result'
213
+ onKeyDown={(e) => handleEnter(e)}
214
+ // onKeyDown={(e) => {
215
+ // if (e.key === 'Enter') {
216
+ // handleSearchClick();
217
+ // }
218
+ // }}
219
+ // disabled={disabled}
220
+ // className={cn(inputVariants({variant, inputSize, className}))}
221
+ ref={(node) => {
222
+ if (typeof ref === 'function') ref(node);
223
+ else if (ref) ref.current = node;
224
+ inputRef.current = node; // Maintain local reference
225
+ }}
226
+ {...props}
227
+ />
228
+ {/* p-[17px] vs p-4 due to the 1px border on the input */}
229
+ {/* right search icon (large only) */}
230
+ <button onClick={handleSearchClick}>
231
+ <span className='hidden lg:flex items-center size-14 p-[17px] bg-[#07192d]
232
+ rounded-tr rounded-br '>
233
+ <img className='' src={SearchIconLg} alt='search icon' />
234
+ </span>
235
+ </button>
236
+ <span className='lg:hidden items-center p-3'>{/* padding 12px? */}
237
+ {/* button --> onClick handler for search */}
238
+ <button className="appearance-none p-0 m-0 border-0 flex items-center justify-center align-middle"
239
+ onClick={ () => selectX() } >
240
+ <img className='' src={CloseIcon} alt='search icon' />
241
+ </button>
242
+ </span>
243
+ </span>
244
+ {/* <div>setSearchResults: {setSearchResults ? 'defined' : 'undefined'}</div> */}
245
+ { (resultsList && !setSearchResults) && (
246
+ <>
247
+ {/* Pads between input and results - allows bottom border of input to be displayed and not covered */}
248
+ <div
249
+ className={`hidden lg:absolute w-[309px] md:w-[334px] lg:w-[293px] top-full -mt-1 pt-1
250
+ focus:outline-hidden -z-10 ms overflow-hidden h-8
251
+ bg-white lg:shadow-[0px_4px_4px_0px_rgba(0,0,0,0.25)]
252
+ border-[#6a686b] border-l border-r lg:border-none
253
+ `
254
+ }
255
+ ></div>
256
+
257
+ <div
258
+ role="listbox"
259
+ ref={resultsRef}
260
+ aria-label="Select option"
261
+ // onKeyDown={handleMenuKeyDown}
262
+ className={`absolute w-[309px] md:w-[334px] lg:w-[293px] top-[calc(100%+4px)] -mt-1 pt-1
263
+ focus:outline-hidden z-10 ms overflow-hidden
264
+ bg-white lg:shadow-[0px_4px_4px_0px_rgba(0,0,0,0.25)]
265
+ border-[#6a686b] border-l border-b border-r lg:border-none
266
+ `
267
+
268
+ // 350-56 - 294
269
+ // inline-flex flex-col justify-start items-start">
270
+
271
+ }
272
+ >
273
+ <hr className="mb-1 -mt-1 mx-2 border-b-0 border-[#A1A6A8] lg:border-none" />
274
+ {/* item.index inside map denotes array index, can use to call out path and title for that item */}
275
+ {resultsList.pages.map((page, index) => (
276
+
277
+ <div key={index} className="ms-4">
278
+ <div className="font-bold text-sm">
279
+ <a role='option' href={page.path} className="text-blue-700 underline">
280
+ {/* */}{page.title && page.title}
281
+ </a>
282
+ </div>
283
+ {page.results.map((result, index) => (
284
+ <div className="ms-4">
285
+ <span className="text-[#1c1d1f]">{resultsList.input}</span>
286
+ <span className="text-[#A1A6A8] lg:text-[#4E4E4E]">{result.characters}</span>
287
+ </div>
288
+ ))}
289
+ </div>
290
+ ))}
291
+
292
+ { resultsList?.pages?.length < 1 &&
293
+ <div className='ms-4 mb-1 text-[#1c1d1f]' >no search results</div>
294
+ }
295
+ </div>
296
+ </>
297
+ )}
298
+ </span>
299
+ );
300
+ }
301
+ );
302
+
303
+ Search.displayName = 'Search';
304
+
@@ -0,0 +1,51 @@
1
+ export interface SearchDataItem {
2
+ path: string;
3
+ title?: string;
4
+ content: string;
5
+ }
6
+
7
+
8
+ export const SearchableData: SearchDataItem[] = [
9
+ {
10
+ path: 'http://localhost:4200/mobile_apps/sdk-component-demo/',
11
+ title: 'SDK Component Demo Site',
12
+ content: `The SDK will act as a single source of truth for components. Developers, UX designers, and other stakeholders can view and interact with components without needing to set up the full application, improving feedback loops and collaboration.
13
+ If you have feedback on this demo site or any of the DHA SDK components, please email us at: b04eb4d0.chenega.onmicrosoft.com@amer.teams.ms
14
+ For installation instructions for the simple-ui package, please visit install instructions`
15
+ },
16
+ {
17
+ path: 'http://localhost:4200/mobile_apps/sdk-component-demo/buttons',
18
+ title: 'Button Component',
19
+ content: `Buttons allow users to interact and make choices within an app with a simple touch. Our Button comes with several variants: default, outlined, and text.
20
+ The button component as built takes in various props in addition to any other html attributes a normal html button would use, for example an aria-label tag.
21
+ A ref can also be created and assigned to the button component, and the component will point the reference to the html button.
22
+ The className prop takes a list of alternate CSS classes the developer would like applied to the component. These are applied using the tailwind twMerge function, and will safely overwrite any conflicting classes ensuring proper styling.
23
+ If you would like the button to display a toggled or selected status as the Usage, Code, and Docs buttons above (changing background color when clicked), the selected and optionally the classNameSelected props can be used, explained below.
24
+ `,
25
+ },
26
+ {
27
+ path: 'http://localhost:4200/mobile_apps/sdk-component-demo/input',
28
+ title: 'Input Component',
29
+ content: `The input component as built takes seven optional props (className, classNameLabel, required, label, labelBaseColor, labelInputcolor, mask, and textShadow), in addition to any other html attributes a normal html form input would use, for example an aria tag.
30
+ The mask prop allows the developer to specify an input mask prop such as (###) ###-### that gaurantees formatting for input data. See usage examples in the drop-down, usage examples, as well as notes in the documentation. Note that the mask is not a placeholder, that is a separate mechanism built into html input elements.
31
+ A reference can also be created and passed to the input component, and the component will point the reference to the input
32
+ The input component as built takes seven optional props in addition to any other html attributes a normal html form input would use, for example an aria tag.
33
+ A reference can also be created and passed to the input component, and the component will point the reference to the input.
34
+ `,
35
+ },
36
+ {
37
+ path: 'http://localhost:4200/mobile_apps/sdk-component-demo/select',
38
+ title: 'Select Component',
39
+ content: `Select components or drop-downs allow a user to make a single selection from multiple choices while preserving space on the form. Our Select component comes with several variants: default, fill, and outline.
40
+ The Select component takes two required props (options and setSelectedOption) as well as multiple optional props in addition to any other html attributes a normal html element would use, for example an aria-tag.
41
+ `,
42
+ },
43
+ {
44
+ path: 'http://localhost:4200/mobile_apps/sdk-component-demo/toggle',
45
+ title: 'Toggle Component',
46
+ content: `A Toggle is an element that allows the user to make a choice between two mutually exclusive states. Our Toggle comes with two variants: default and outlined
47
+ The Toggle component takes a variant prop used to indicate pre-defined styles, an optional click handler, and various other optional props described below and uses those to display a Toggle button. The variant prop currently takes one of two values, those possible values currently being 'default' and 'outlined'.
48
+ As with the button component, Toggle takes any other additional html attributes and passes them to the parent <button> tag. This div is styled using the tailwind twMerge() utility, so custom classes can be added via the classNames prop as and applied as well.
49
+ `,
50
+ }
51
+ ]
@@ -0,0 +1,98 @@
1
+ // Badge.stories.tsx
2
+
3
+ // import { Meta, StoryFn} from '@storybook/react';
4
+ import { SectionHeader } from './SectionHeader';
5
+ import { Meta, StoryContext, StoryFn, StoryObj } from '@storybook/react';
6
+ import { Input, InputProps } from './Input';
7
+ import { Button } from './Button';
8
+ import { CharacterCounter, CharacterCounterProps } from './CharacterCounter';
9
+ import { userEvent, within } from 'storybook/test';
10
+ import { expect } from 'storybook/test';
11
+ import { FC, useRef, useState } from 'react';
12
+ import frame from '../assets/img/Frame.svg';
13
+ import check from '../assets/img/check.svg';
14
+ import chevronRight from '../assets/img/chevron-right.svg';
15
+
16
+ // Import your images
17
+ import firstAidKit from '../assets/img/first-aid-kit.svg';
18
+ import heartbeat from '../assets/img/heartbeat.svg';
19
+ import pill from '../assets/img/pill.svg';
20
+ import prescription from '../assets/img/prescription.svg';
21
+ import stethoscope from '../assets/img/stethoscope.svg';
22
+ // import { useState } from 'react';
23
+
24
+
25
+ export default {
26
+ title: 'Components/SectionHeader',
27
+ component: SectionHeader,
28
+ args: {
29
+
30
+ },
31
+ parameters: {
32
+ layout: 'centered',
33
+ backgrounds: { default: 'light' },
34
+ },
35
+ } as Meta<typeof SectionHeader>;
36
+
37
+ /*
38
+ className?: string;
39
+ leftIcon?: ReactNode; // displays left icon if present
40
+ rightIcon?: ReactNode; // displays right icon if present
41
+ underline?: boolean; // display or not the underline
42
+ subHeader?: string; // if present, displays subHeader below primary Header line
43
+ */
44
+
45
+ // DefaultBadge story
46
+ export const DefaultSectionHeader = {
47
+ args: {
48
+ className: 'w-[600px]',
49
+ children: "I'm a Header!",
50
+ iconRight: <img src={frame} alt='left icon' className='size-10' />,
51
+ iconLeft: <img src={check} alt='right icon' className='size-8' />,
52
+ underline: true,
53
+ subHeader: "Subheader Here",
54
+ childButton: <Button variant='transparent' className='h-10'
55
+ icon={<img src={chevronRight} alt='right chevron' />}
56
+ iconPosition='right' >Click Me</Button>
57
+ }
58
+ };
59
+
60
+ // DefaultBadge story
61
+ export const NoUnderline = {
62
+ args: {
63
+ className: 'w-[600px]',
64
+ children: "I'm a Header!",
65
+ iconLeft: <img src={frame} alt='left icon' className='size-10' />,
66
+ iconRight: <img src={check} alt='right icon' className='size-8' />,
67
+ subHeader: "Subheader Here",
68
+ }
69
+ };
70
+
71
+ // DefaultBadge story
72
+ export const NoSubHeaderLeftIconOnly = {
73
+ args: {
74
+ className: 'w-[600px]',
75
+ children: "I'm a Header!",
76
+ iconLeft: <img src={frame} alt='left icon' className='size-10' />,
77
+ underline: true,
78
+ }
79
+ };
80
+
81
+ // DefaultBadge story
82
+ export const NoLeftIconNoSubHeader = {
83
+ args: {
84
+ className: 'w-[600px]',
85
+ children: "I'm a Header!",
86
+ iconRight: <img src={check} alt='right icon' className='size-8' />,
87
+ underline: true,
88
+ }
89
+ };
90
+
91
+ export const NoLeftIconNoSubHeaderNoUnderline = {
92
+ args: {
93
+ className: 'w-[600px]',
94
+ children: "I'm a Header!",
95
+ iconLeft: <img src={frame} alt='left icon' className='size-10' />,
96
+ iconRight: <img src={check} alt='right icon' className='size-8' />,
97
+ }
98
+ };
@@ -0,0 +1,60 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { SectionHeader } from './SectionHeader';
4
+
5
+ describe('SectionHeader Component', () => {
6
+ it('renders the main header text', () => {
7
+ render(<SectionHeader>Test Header</SectionHeader>);
8
+ expect(screen.getByText('Test Header')).toBeInTheDocument();
9
+ });
10
+
11
+ it('merges default classes with custom className', () => {
12
+ const customClass = 'custom-class';
13
+ render(<SectionHeader className={customClass}>Test Header</SectionHeader>);
14
+ // The outer div should include the custom class along with default classes.
15
+ const outerDiv = screen.getByText('Test Header').parentElement?.parentElement?.parentElement;
16
+ expect(outerDiv).toHaveClass('custom-class');
17
+ });
18
+
19
+ it('renders a section header with a button, type = section and fill = true', () => {
20
+ render(<SectionHeader button fill type='section'>Test Header</SectionHeader>);
21
+ expect(screen.getByText('Test Header')).toBeInTheDocument();;
22
+ });
23
+
24
+ it('renders a section header of type subsection, applies underline classes when underline prop is true', () => {
25
+ render(<SectionHeader type='subsection' underline>Test Header</SectionHeader>);
26
+ const outerDiv = screen.getByText('Test Header').parentElement?.parentElement?.parentElement;
27
+ expect(outerDiv).toHaveClass("border-b-2 border-[#07192D]");
28
+ });
29
+
30
+ it('renders a subHeader when provided', () => {
31
+ render(
32
+ <SectionHeader subHeader="Sub Header">
33
+ Test Header
34
+ </SectionHeader>
35
+ );
36
+ expect(screen.getByText('Sub Header')).toBeInTheDocument();
37
+ // Check for one of the classes applied to subHeader text.
38
+ expect(screen.getByText('Sub Header')).toHaveClass(/text-xl/);
39
+ });
40
+
41
+ it('renders iconLeft when provided', () => {
42
+ render(
43
+ <SectionHeader iconLeft={<span data-testid="icon-left">IconLeft</span>}>
44
+ Test Header
45
+ </SectionHeader>
46
+ );
47
+ expect(screen.getByTestId('icon-left')).toBeInTheDocument();
48
+ expect(screen.getByTestId('icon-left')).toHaveTextContent('IconLeft');
49
+ });
50
+
51
+ it('renders iconRight when provided', () => {
52
+ render(
53
+ <SectionHeader iconRight={<span data-testid="icon-right">IconRight</span>}>
54
+ Test Header
55
+ </SectionHeader>
56
+ );
57
+ expect(screen.getByTestId('icon-right')).toBeInTheDocument();
58
+ expect(screen.getByTestId('icon-right')).toHaveTextContent('IconRight');
59
+ });
60
+ });
@@ -0,0 +1,91 @@
1
+ import { forwardRef, HTMLAttributes, ReactNode, useEffect, useState } from 'react';
2
+ import { twMerge } from 'tailwind-merge';
3
+ import { Button } from './Button';
4
+
5
+ import chevronRight from '../assets/img/chevron-right.svg';
6
+ import chevronRightLight from '../assets/img/chevron-right-light.svg';
7
+
8
+ export interface SectionHeaderProps extends HTMLAttributes<HTMLDivElement> {
9
+ className?: string;
10
+ classNameChildren?: string;
11
+ classNameSubHeader?: string;
12
+ iconLeft?: ReactNode | undefined; // displays left icon if present
13
+ iconRight?: ReactNode | undefined; // displays right icon if present
14
+ underline?: boolean; // display or not the underline
15
+ subHeader?: string; // if present, displays subHeader below primary Header line
16
+ fill?: boolean;
17
+ button?: boolean;
18
+ type?: 'page' | 'section' | 'subsection';
19
+ buttonOnClick?: () => void;
20
+ buttonContent?: string;
21
+ padLeft?: boolean;
22
+ padRight?: boolean;
23
+ }
24
+
25
+ export const SectionHeader = forwardRef<HTMLDivElement, SectionHeaderProps>(
26
+ ({ className = '', classNameChildren = '', classNameSubHeader = '', iconLeft, iconRight,
27
+ underline = false, subHeader = false, fill = false, type = 'page', padLeft = true,
28
+ padRight = true, button, buttonOnClick, buttonContent = 'Click Me',
29
+ children, ...props }, ref) => {
30
+
31
+ const [outerClasses, setOuterClasses] = useState<string>('');
32
+ const [headerClasses, setHeaderClasses] = useState<string>('');
33
+ const [subHeaderClasses, setSubHeaderClasses] = useState<string>('');
34
+ // top & bottom padding different for type === page
35
+ const defaultClasses = `${type === 'page' ? 'pt-4 pb-2' : 'py-2'} flex pl-6 pr-2 justify-start items-center gap-4 inline-flex w-full`;
36
+
37
+ // dark text - text-[#07192d]
38
+ // light text - text-[#f0f0f0]
39
+
40
+ // build default css classes w/ twMerge
41
+ useEffect (() => {
42
+ const classUnderLine = underline ? 'border-b-2 border-[#07192D]' : '';
43
+ const classFill = fill ? 'bg-[#092068] text-[#f0f0f0]' : 'bg-transparent text-[#07192d]';
44
+ const padding = padLeft && padRight ? 'mx-4 ' : padLeft ? 'ms-4' : padRight ? 'me-4' : '';
45
+ setOuterClasses(twMerge(defaultClasses, classUnderLine, classFill, padding, className));
46
+ }, [className, underline, fill, defaultClasses, padLeft, padRight]);
47
+
48
+ // console.log('outerClasses: ', outerClasses);
49
+
50
+ useEffect (() => {
51
+ setHeaderClasses(twMerge(`self-stretch ${ type === 'page' ? 'text-[40px]' : type === 'section' ? ' text-[32px]' : 'text-2xl'} font-normal font-["Arial"] leading-[48px]`, classNameChildren));
52
+ }, [classNameChildren, type]);
53
+
54
+ useEffect (() => {
55
+ setSubHeaderClasses(twMerge(`${type === 'page' ? 'text-xl' : 'text-lg'} font-normal font-["Arial"] leading-[30px]`, classNameSubHeader));
56
+ }, [classNameSubHeader, type]);
57
+
58
+ // type?: 'page' | 'section' | 'subsection';
59
+ // page - icon 40x40px, text 40px, sub-header 30px
60
+ // section - icon 24x24px, text 32px, sub-header text-lg(18px)
61
+ // subsection - icon 24x24px, text 2xl(24px), sub-header text-lg(18px)
62
+
63
+ return (
64
+ <div className={twMerge(outerClasses, className)}>
65
+ {/* justify-story --> start at left side, items-center --> vertical middle of flex row */}
66
+ <div className="grow shrink basis-0 justify-start items-center gap-4 flex flex-row">
67
+
68
+ {iconLeft && iconLeft }
69
+
70
+ <div className="flex-col justify-start items-start inline-flex w-full">
71
+ <div className={headerClasses}
72
+ >{children}</div>
73
+ {subHeader && <div className={subHeaderClasses}
74
+ >{subHeader}</div>}
75
+ </div>
76
+
77
+ { iconRight && iconRight }
78
+
79
+ { button &&
80
+
81
+ // text color and icon correctly set based on fill value
82
+ <Button variant='transparent' className={`h-[48px] text-black ${fill && 'text-[#f0f0f0] active:bg-blue-700 hover:bg-[#092068]'}
83
+ ${ type === 'page' ? 'text-base' : 'text-sm'} }`}
84
+ icon={<img src={fill ? chevronRightLight : chevronRight} alt='right chevron' />}
85
+ iconPosition='right' onClick={buttonOnClick} >{buttonContent}</Button>
86
+ }
87
+
88
+ </div>
89
+ </div>
90
+ );
91
+ });