@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,311 @@
|
|
|
1
|
+
import { useEffect, useRef, useState, HTMLAttributes } from 'react';
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
import chevronDown from '../assets/img/chevron-down.svg';
|
|
4
|
+
import chevronUp from '../assets/img/chevron-up.svg';
|
|
5
|
+
import chevronDownWhite from '../assets/img/chevron-down-white.svg';
|
|
6
|
+
import chevronUpWhite from '../assets/img/chevron-up-white.svg';
|
|
7
|
+
|
|
8
|
+
interface VariantType {
|
|
9
|
+
[key: string]: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const baseClasses = 'outline-hidden outline-offset-0 flex justify-between items-center w-full h-14 ' +
|
|
14
|
+
'border focus:outline-4 focus:mb-2 focus:outline-[#fa89f1] shadow-xs pl-4 pr-2 py-2 bg-white ' +
|
|
15
|
+
'text-base md:text-lg font-medium text-gray-700 hover:bg-gray-50 border-[#b3b3b3] h-12 mt-1 font-["Arial"] ';
|
|
16
|
+
|
|
17
|
+
const variants: VariantType = {
|
|
18
|
+
default: 'hover:bg-gray-200 text-black' +
|
|
19
|
+
'hover:border-gray-400 disabled:bg-dha-mc-bottom-nav-background disabled:text-dha-mc-checkbox-inactive ' +
|
|
20
|
+
'disabled:border-dha-mc-secondary-border disabled:border-2',
|
|
21
|
+
fill: 'hover:bg-[#0c2c8e] text-white bg-[#092068] ' +
|
|
22
|
+
'hover:border-gray-400 disabled:bg-dha-mc-bottom-nav-background disabled:text-dha-mc-checkbox-inactive ' +
|
|
23
|
+
'disabled:border-dha-mc-secondary-border disabled:border-2 py-3',
|
|
24
|
+
outline: 'bg-white disabled:border-dha-mc-secondary-border ' +
|
|
25
|
+
'disabled:text-dha-mc-checkbox-inactive',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type Option = {
|
|
29
|
+
name: string;
|
|
30
|
+
value?: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export interface SelectProps extends HTMLAttributes<HTMLDivElement> {
|
|
34
|
+
className?: string;
|
|
35
|
+
classNameContainer?: string;
|
|
36
|
+
label?: string;
|
|
37
|
+
variant?: string;
|
|
38
|
+
options: Option[];
|
|
39
|
+
optionsLabel?: string;
|
|
40
|
+
disabled?: boolean;
|
|
41
|
+
error?: boolean;
|
|
42
|
+
width?: string;
|
|
43
|
+
setSelectedOption: (param: string) => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const Select = ({
|
|
47
|
+
className,
|
|
48
|
+
classNameContainer = '',
|
|
49
|
+
label,
|
|
50
|
+
options,
|
|
51
|
+
optionsLabel,
|
|
52
|
+
disabled = false,
|
|
53
|
+
variant = 'default',
|
|
54
|
+
setSelectedOption,
|
|
55
|
+
error = false,
|
|
56
|
+
width = '',
|
|
57
|
+
...props
|
|
58
|
+
}: SelectProps) => {
|
|
59
|
+
|
|
60
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
61
|
+
const buttonRef = useRef<HTMLButtonElement>(null);
|
|
62
|
+
const dropdownRef = useRef<HTMLDivElement>(null); // ref to drop down portion only, determine if should display above/below
|
|
63
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
64
|
+
const [isAbove, setIsAbove] = useState(false); // use to determine/display above/below button depending on avail space
|
|
65
|
+
const [selectedOptionName, setSelectedOptionName] = useState(optionsLabel || 'Options');
|
|
66
|
+
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
|
|
67
|
+
const buttonHeight = useRef<string>('bottom-[60px]');
|
|
68
|
+
const [errorCss, setErrorCss] = useState<string>('');
|
|
69
|
+
// const [buttonHeight, setButtonHeight] = useState<string>('bottom-[45px]');
|
|
70
|
+
// const [buttonHeight, setButtonHeight] = useState<number | null>(null);
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
useEffect (() => {
|
|
74
|
+
if (buttonRef.current) {
|
|
75
|
+
const buttonRect = buttonRef.current.getBoundingClientRect();
|
|
76
|
+
// setButtonHeight(buttonRect.height);
|
|
77
|
+
const height = Math.floor(buttonRect.height) + 2;
|
|
78
|
+
buttonHeight.current = 'bottom-[' + height + 'px]';
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
// console.log('button height: ', buttonHeight.current);
|
|
82
|
+
// setButtonHeight('bottom-[' + Math.floor(buttonRect.height + 4) + 'px]');
|
|
83
|
+
// console.log('button height: ', buttonHeight);
|
|
84
|
+
}
|
|
85
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
86
|
+
}, [buttonRef.current]);
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
// Function to calculate dropdown position
|
|
90
|
+
const updateDropdownPosition = () => {
|
|
91
|
+
if (containerRef.current && dropdownRef.current) {
|
|
92
|
+
const buttonRect = containerRef.current.getBoundingClientRect();
|
|
93
|
+
const dropdownHeight = dropdownRef.current.offsetHeight;
|
|
94
|
+
|
|
95
|
+
// console.log('dropdownHeight: ', dropdownHeight);
|
|
96
|
+
|
|
97
|
+
const windowHeight = window.innerHeight;
|
|
98
|
+
const documentHeight = document.body.offsetHeight;
|
|
99
|
+
|
|
100
|
+
// THIS IS THE SECRET SAUCE!
|
|
101
|
+
const containerHeight = windowHeight > documentHeight ? documentHeight : windowHeight;
|
|
102
|
+
|
|
103
|
+
// console.log('windowHeight: ', windowHeight);
|
|
104
|
+
// console.log('documentHeight: ', documentHeight);
|
|
105
|
+
// console.log('calculated containerHeight: ', containerHeight);
|
|
106
|
+
// console.log('buttonRect.bottom: ', buttonRect.bottom);
|
|
107
|
+
|
|
108
|
+
// Check if there's enough space below the button
|
|
109
|
+
const spaceBelow = containerHeight - buttonRect.bottom;
|
|
110
|
+
if (spaceBelow < dropdownHeight && buttonRect.top > dropdownHeight) {
|
|
111
|
+
setIsAbove(true); // Flip above if not enough space below
|
|
112
|
+
} else {
|
|
113
|
+
setIsAbove(false); // Default to below
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// array of refs, one for each dropdown item
|
|
119
|
+
const itemRefs = useRef<HTMLButtonElement[]>([]);
|
|
120
|
+
|
|
121
|
+
// We might slice the array in case `options` changes
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
itemRefs.current = itemRefs.current.slice(0, options.length);
|
|
124
|
+
}, [options]);
|
|
125
|
+
|
|
126
|
+
// Track which item is "focused" wrt arrow navigation
|
|
127
|
+
const [focusedIndex, setFocusedIndex] = useState<number>(0);
|
|
128
|
+
|
|
129
|
+
const toggleDropdown = () => {
|
|
130
|
+
setIsOpen(!isOpen);
|
|
131
|
+
setFocusedIndex(0);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
// px-4
|
|
136
|
+
// On Error, border-2 border-[#b3b3b3]
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
error ? setErrorCss('border-2 border-[#ff0004] pl-[15px]') : setErrorCss('');
|
|
139
|
+
}, [error]);
|
|
140
|
+
|
|
141
|
+
// Close on click outside, ESC, focus outside, etc.
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
|
|
144
|
+
function checkIfClickedOutside(event: MouseEvent | TouchEvent) {
|
|
145
|
+
if (isOpen && containerRef.current && !containerRef.current.contains(event.target as Node)) {
|
|
146
|
+
setIsOpen(false);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
150
|
+
if (isOpen && event.key === 'Escape') {
|
|
151
|
+
setIsOpen(false);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function handleFocusIn(event: FocusEvent) {
|
|
155
|
+
if (isOpen && containerRef.current && !containerRef.current.contains(event.target as Node)) {
|
|
156
|
+
setIsOpen(false);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
document.addEventListener('mousedown', checkIfClickedOutside);
|
|
161
|
+
document.addEventListener('touchend', checkIfClickedOutside);
|
|
162
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
163
|
+
document.addEventListener('focusin', handleFocusIn);
|
|
164
|
+
|
|
165
|
+
return () => {
|
|
166
|
+
document.removeEventListener('mousedown', checkIfClickedOutside);
|
|
167
|
+
document.removeEventListener('touchend', checkIfClickedOutside);
|
|
168
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
169
|
+
document.removeEventListener('focusin', handleFocusIn);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
}, [isOpen]);
|
|
173
|
+
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
|
|
176
|
+
if (isOpen) {
|
|
177
|
+
updateDropdownPosition();
|
|
178
|
+
// Optional: Recalculate on window resize
|
|
179
|
+
// window.addEventListener("resize", updateDropdownPosition);
|
|
180
|
+
// return () => window.removeEventListener("resize", updateDropdownPosition);
|
|
181
|
+
}
|
|
182
|
+
}, [isOpen]);
|
|
183
|
+
|
|
184
|
+
// Whenever focusedIndex changes, focus the button at that index
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
if (isOpen) {
|
|
187
|
+
itemRefs.current[focusedIndex]?.focus();
|
|
188
|
+
}
|
|
189
|
+
}, [focusedIndex, isOpen]);
|
|
190
|
+
|
|
191
|
+
// Key Down handler for the "menu" DIV.
|
|
192
|
+
const handleMenuKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
193
|
+
if (!isOpen) return;
|
|
194
|
+
|
|
195
|
+
switch (event.key) {
|
|
196
|
+
case 'ArrowDown':
|
|
197
|
+
event.preventDefault();
|
|
198
|
+
setFocusedIndex((prev) => {
|
|
199
|
+
const nextIndex = (prev + 1) % options.length;
|
|
200
|
+
setSelectedIndex(nextIndex);
|
|
201
|
+
return nextIndex;
|
|
202
|
+
});
|
|
203
|
+
break;
|
|
204
|
+
case 'ArrowUp':
|
|
205
|
+
event.preventDefault();
|
|
206
|
+
setFocusedIndex((prev) => {
|
|
207
|
+
const nextIndex = (prev - 1 + options.length) % options.length;
|
|
208
|
+
setSelectedIndex(nextIndex);
|
|
209
|
+
return nextIndex;
|
|
210
|
+
});
|
|
211
|
+
break;
|
|
212
|
+
case 'Home':
|
|
213
|
+
event.preventDefault();
|
|
214
|
+
setFocusedIndex(0);
|
|
215
|
+
break;
|
|
216
|
+
case 'End':
|
|
217
|
+
event.preventDefault();
|
|
218
|
+
setFocusedIndex(options.length - 1);
|
|
219
|
+
break;
|
|
220
|
+
default:
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<div
|
|
228
|
+
className={twMerge('relative inline-block text-left w-[292px] md:w-[343px] lg:w-[600px] max-w-full', classNameContainer)}
|
|
229
|
+
ref={containerRef}
|
|
230
|
+
{...props}
|
|
231
|
+
aria-disabled={disabled}
|
|
232
|
+
>
|
|
233
|
+
{label ? <p className="text-black mb-2 text-base font-['Arial']">{label}</p> : ''}
|
|
234
|
+
|
|
235
|
+
<button
|
|
236
|
+
onClick={toggleDropdown}
|
|
237
|
+
ref={buttonRef}
|
|
238
|
+
className={twMerge(baseClasses, variants[variant] || variants.default, errorCss, className,
|
|
239
|
+
(isOpen && isAbove && "rounded-b-lg") || (isOpen && !isAbove && 'rounded-t-lg') || 'rounded-lg')}
|
|
240
|
+
aria-label={'Select options - ' + selectedOptionName}
|
|
241
|
+
aria-disabled={disabled}
|
|
242
|
+
aria-expanded={isOpen}
|
|
243
|
+
aria-haspopup='listbox'
|
|
244
|
+
disabled={disabled}
|
|
245
|
+
tabIndex={0}
|
|
246
|
+
>
|
|
247
|
+
{selectedOptionName}
|
|
248
|
+
{isOpen ? variant === 'fill' ? <img src={chevronUpWhite} alt='chevron up' /> : <img src={chevronUp} alt='chevron up' />
|
|
249
|
+
: variant === 'fill' ? <img src={chevronDownWhite} alt='chevron down' /> : <img src={chevronDown} alt='chevron down' /> }
|
|
250
|
+
</button>
|
|
251
|
+
|
|
252
|
+
{isOpen && (
|
|
253
|
+
<div
|
|
254
|
+
role="listbox"
|
|
255
|
+
ref={dropdownRef}
|
|
256
|
+
aria-label="Select option"
|
|
257
|
+
onKeyDown={handleMenuKeyDown}
|
|
258
|
+
className={twMerge("absolute w-full shadow-lg bg-white ring-1 ring-white/5 focus:outline-hidden z-10 ",
|
|
259
|
+
((isOpen && isAbove) && 'rounded-t-lg pt-1') || ((isOpen && !isAbove) && 'rounded-b-lg pb-1'),
|
|
260
|
+
isAbove && buttonHeight.current)}
|
|
261
|
+
>
|
|
262
|
+
{/* <ul
|
|
263
|
+
className="list"
|
|
264
|
+
role="listbox"
|
|
265
|
+
aria-label="Select option"
|
|
266
|
+
onKeyDown={handleMenuKeyDown}
|
|
267
|
+
> */}
|
|
268
|
+
{options.map((item, index) => (
|
|
269
|
+
// <li key={item.name}>
|
|
270
|
+
<button
|
|
271
|
+
// ref={buttonRef}
|
|
272
|
+
key={item.name}
|
|
273
|
+
ref={el => {
|
|
274
|
+
itemRefs.current[index] = el!
|
|
275
|
+
}} // store ref
|
|
276
|
+
className='block w-full px-4 py-2 text-left text-base text-gray-700
|
|
277
|
+
hover:bg-[#092068]/20 focus:outline-hidden focus:bg-[#092068]/20'
|
|
278
|
+
role="option"
|
|
279
|
+
aria-selected={selectedIndex === index}
|
|
280
|
+
aria-label={'option ' + item.name}
|
|
281
|
+
onClick={() => {
|
|
282
|
+
setSelectedIndex(index);
|
|
283
|
+
setSelectedOptionName(item.name);
|
|
284
|
+
setSelectedOption(item.value || item.name);
|
|
285
|
+
setIsOpen(false);
|
|
286
|
+
}}
|
|
287
|
+
onKeyDown={(event) => {
|
|
288
|
+
if (
|
|
289
|
+
event.key === 'Enter' ||
|
|
290
|
+
event.code === 'Enter' ||
|
|
291
|
+
event.key === ' '
|
|
292
|
+
) {
|
|
293
|
+
setSelectedIndex(index);
|
|
294
|
+
setSelectedOptionName(item.name);
|
|
295
|
+
setSelectedOption(item.value || item.name);
|
|
296
|
+
setIsOpen(false);
|
|
297
|
+
} else if (event.key === 'Escape') {
|
|
298
|
+
setIsOpen(false);
|
|
299
|
+
}
|
|
300
|
+
}}
|
|
301
|
+
>
|
|
302
|
+
{item.name}
|
|
303
|
+
</button>
|
|
304
|
+
// </li>
|
|
305
|
+
))}
|
|
306
|
+
{/* </ul> */}
|
|
307
|
+
</div>
|
|
308
|
+
)}
|
|
309
|
+
</div>
|
|
310
|
+
);
|
|
311
|
+
};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// Shield.stories.tsx
|
|
2
|
+
|
|
3
|
+
// import { Meta, StoryFn} from '@storybook/react';
|
|
4
|
+
import { Shield } from './Shield';
|
|
5
|
+
import { Meta, StoryContext, StoryFn, StoryObj } from '@storybook/react';
|
|
6
|
+
import { Input, InputProps } from './Input';
|
|
7
|
+
import { CharacterCounter, CharacterCounterProps } from './CharacterCounter';
|
|
8
|
+
import { userEvent, within } from 'storybook/test';
|
|
9
|
+
import { expect } from 'storybook/test';
|
|
10
|
+
import { FC, useRef, useState } from 'react';
|
|
11
|
+
|
|
12
|
+
// Import your images
|
|
13
|
+
import firstAidKit from '../assets/img/first-aid-kit.svg';
|
|
14
|
+
import heartbeat from '../assets/img/heartbeat.svg';
|
|
15
|
+
import pill from '../assets/img/pill.svg';
|
|
16
|
+
import prescription from '../assets/img/prescription.svg';
|
|
17
|
+
import stethoscope from '../assets/img/stethoscope.svg';
|
|
18
|
+
// import { useState } from 'react';
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
export default {
|
|
22
|
+
title: 'Components/Shield',
|
|
23
|
+
component: Shield,
|
|
24
|
+
argTypes: {
|
|
25
|
+
variant: {
|
|
26
|
+
control: 'select',
|
|
27
|
+
options: ['default', 'icon', 'media']
|
|
28
|
+
},
|
|
29
|
+
subVariant: {
|
|
30
|
+
control: 'select',
|
|
31
|
+
options: ['default', 'gray', 'red', 'green', 'yellow', 'blue'],
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
parameters: {
|
|
35
|
+
layout: 'centered',
|
|
36
|
+
backgrounds: { default: 'light' },
|
|
37
|
+
},
|
|
38
|
+
} as Meta<typeof Shield>;
|
|
39
|
+
|
|
40
|
+
// DefaultShield story
|
|
41
|
+
export const DefaultShield = {
|
|
42
|
+
args: {
|
|
43
|
+
variant: 'default',
|
|
44
|
+
subVariant: 'default',
|
|
45
|
+
children: 'Shield!',
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// DefaultBlueShield story
|
|
50
|
+
export const DefaultBlueShield = {
|
|
51
|
+
args: {
|
|
52
|
+
variant: 'default',
|
|
53
|
+
subVariant: 'blue',
|
|
54
|
+
children: 'Shield!',
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// DefaultIconShield story
|
|
59
|
+
export const DefaultIconShield = {
|
|
60
|
+
args: {
|
|
61
|
+
variant: 'icon',
|
|
62
|
+
subVariant: 'default',
|
|
63
|
+
children: 'Shield!',
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
// WarningHalf story
|
|
69
|
+
// export const WarningHalfShield = {
|
|
70
|
+
// args: {
|
|
71
|
+
// variant: 'warning',
|
|
72
|
+
// subVariant: 'half',
|
|
73
|
+
// children: 'Shield!',
|
|
74
|
+
// }
|
|
75
|
+
// };
|
|
76
|
+
|
|
77
|
+
export const WarningHalfShield: StoryFn = () => {
|
|
78
|
+
return (
|
|
79
|
+
<div className='w-[600px] h-[300px] border-l border-r border-slate-300'>
|
|
80
|
+
<Shield variant='warning' subVariant='half'>I am a most excellent Shield, carefully designed by UX.</Shield>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
export const ShieldTestClose: StoryFn = () => {
|
|
87
|
+
return (
|
|
88
|
+
<div className='w-[600px] h-[300px] border-l border-r border-slate-300'>
|
|
89
|
+
<Shield variant='warning' subVariant='half'>I am a most excellent Shield, carefully designed by UX.</Shield>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
// Attach the play function after the story definition:
|
|
96
|
+
ShieldTestClose.play = async ({ canvasElement }: StoryContext) => {
|
|
97
|
+
const canvas = within(canvasElement);
|
|
98
|
+
|
|
99
|
+
// Example: Locate an element labeled "Close modal" and click it.
|
|
100
|
+
const closeButton = canvas.getByLabelText('Close modal');
|
|
101
|
+
await userEvent.click(closeButton);
|
|
102
|
+
|
|
103
|
+
expect(closeButton).not.toBeVisible();
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
export const WarningFullShield: StoryFn = () => {
|
|
108
|
+
return (
|
|
109
|
+
<div className='w-[600px] h-[300px] border-l border-r border-slate-300'>
|
|
110
|
+
<Shield variant='warning' subVariant='full'>
|
|
111
|
+
I am a most excellent Shield, carefully designed by UX, and implemented
|
|
112
|
+
by the SDK Team. For more information, check out the Documentation and
|
|
113
|
+
Usage examples.
|
|
114
|
+
</Shield>
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
export const HazardHalfShield: StoryFn = () => {
|
|
121
|
+
return (
|
|
122
|
+
<div className='w-[600px] h-[300px] border-l border-r border-slate-300'>
|
|
123
|
+
<Shield variant='hazard' subVariant='half'>I am a most excellent Shield, carefully designed by UX.</Shield>
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export const HazardFullShield: StoryFn = () => {
|
|
129
|
+
return (
|
|
130
|
+
<div className='w-[600px] h-[300px] border-l border-r border-slate-300'>
|
|
131
|
+
<Shield variant='hazard' subVariant='full'>
|
|
132
|
+
I am a most excellent Shield, carefully designed by UX, and implemented
|
|
133
|
+
by the SDK Team. For more information, check out the Documentation and
|
|
134
|
+
Usage examples.
|
|
135
|
+
</Shield>
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export const GoHalfShield: StoryFn = () => {
|
|
141
|
+
return (
|
|
142
|
+
<div className='w-[600px] h-[300px] border-l border-r border-slate-300'>
|
|
143
|
+
<Shield variant='go' subVariant='half'>I am a most excellent Shield, carefully designed by UX.</Shield>
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export const GoFullShield: StoryFn = () => {
|
|
149
|
+
return (
|
|
150
|
+
<div className='w-[600px] h-[300px] border-l border-r border-slate-300'>
|
|
151
|
+
<Shield variant='go' subVariant='full'>
|
|
152
|
+
I am a most excellent Shield, carefully designed by UX, and implemented
|
|
153
|
+
by the SDK Team. For more information, check out the Documentation and
|
|
154
|
+
Usage examples.
|
|
155
|
+
</Shield>
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
// GreenIconShield story
|
|
162
|
+
export const GreenIconShield = {
|
|
163
|
+
args: {
|
|
164
|
+
variant: 'icon',
|
|
165
|
+
subVariant: 'green',
|
|
166
|
+
children: 'Shield!',
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// CustomIconShield story
|
|
171
|
+
export const CustomIconShield = {
|
|
172
|
+
args: {
|
|
173
|
+
variant: 'icon',
|
|
174
|
+
subVariant: 'custom',
|
|
175
|
+
className: 'inline-flex items-center gap-x-1.5 rounded-full bg-red-100 px-2 py-1 text-xs font-medium text-red-600',
|
|
176
|
+
classNameSvg: 'fill-red-400 stroke-red-400',
|
|
177
|
+
children: 'Shield!',
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
// DefaultMediaShield story
|
|
183
|
+
export const DefaultMediaShield = {
|
|
184
|
+
args: {
|
|
185
|
+
variant: 'media',
|
|
186
|
+
subVariant: 'default',
|
|
187
|
+
children: <>
|
|
188
|
+
<h5 className='text-sm font-bold my-2'>Flu Season!</h5>
|
|
189
|
+
<p>Don't be caught unprepared. </p>
|
|
190
|
+
</>,
|
|
191
|
+
imagePath: 'src/assets/img/heartbeat.svg',
|
|
192
|
+
imageAlt: 'heartbeat icon',
|
|
193
|
+
classNameImage: 'mt-1',
|
|
194
|
+
className: 'pb-2',
|
|
195
|
+
},
|
|
196
|
+
};
|