@dhasdk/simple-ui 1.0.8 → 1.0.10

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 (228) hide show
  1. package/README.md +2 -2
  2. package/index.css +1 -0
  3. package/{src/index.ts → index.d.ts} +8 -15
  4. package/index.js +34 -0
  5. package/index.mjs +5473 -0
  6. package/lib/Accordion.d.ts +36 -0
  7. package/lib/AppointmentPicker.d.ts +21 -0
  8. package/lib/Badge.d.ts +11 -0
  9. package/lib/Breadcrumbs.d.ts +13 -0
  10. package/lib/Button.d.ts +15 -0
  11. package/lib/ButtonGroup.d.ts +8 -0
  12. package/lib/Card.d.ts +11 -0
  13. package/lib/CharacterCounter.d.ts +11 -0
  14. package/lib/CheckBox.d.ts +30 -0
  15. package/lib/DatePicker.d.ts +7 -0
  16. package/lib/Input.d.ts +16 -0
  17. package/lib/List.d.ts +22 -0
  18. package/lib/Modal.d.ts +18 -0
  19. package/lib/Pill.d.ts +13 -0
  20. package/lib/ProgressBar.d.ts +19 -0
  21. package/lib/RadioGroup.d.ts +16 -0
  22. package/lib/RadioIcon.d.ts +3 -0
  23. package/lib/Search.d.ts +26 -0
  24. package/lib/SearchContent.d.ts +6 -0
  25. package/lib/SectionHeader.d.ts +18 -0
  26. package/lib/Select.d.ts +19 -0
  27. package/lib/Shield.d.ts +12 -0
  28. package/lib/SideBarNav.d.ts +21 -0
  29. package/lib/Skeleton.d.ts +15 -0
  30. package/lib/SkipLink.d.ts +23 -0
  31. package/lib/Slider.d.ts +14 -0
  32. package/lib/Status.d.ts +10 -0
  33. package/lib/Tabs.d.ts +26 -0
  34. package/lib/Toggle.d.ts +11 -0
  35. package/lib/Tooltip.d.ts +14 -0
  36. package/package.json +1 -1
  37. package/.babelrc +0 -12
  38. package/.storybook/main.ts +0 -35
  39. package/.storybook/preview.ts +0 -4
  40. package/BAKpostcss.config.jsBAK +0 -15
  41. package/BAKtailwind.config.mjsBAK +0 -99
  42. package/coverage/storybook/coverage-storybook.json +0 -32411
  43. package/coverage/storybook/lcov-report/Accordion.tsx.html +0 -805
  44. package/coverage/storybook/lcov-report/Badge.tsx.html +0 -346
  45. package/coverage/storybook/lcov-report/Breadcrumbs.tsx.html +0 -742
  46. package/coverage/storybook/lcov-report/Button.tsx.html +0 -448
  47. package/coverage/storybook/lcov-report/ButtonGroup.tsx.html +0 -403
  48. package/coverage/storybook/lcov-report/Card.tsx.html +0 -292
  49. package/coverage/storybook/lcov-report/CharacterCounter.tsx.html +0 -253
  50. package/coverage/storybook/lcov-report/CheckBox.tsx.html +0 -1555
  51. package/coverage/storybook/lcov-report/DatePicker.tsx.html +0 -826
  52. package/coverage/storybook/lcov-report/Input.tsx.html +0 -1012
  53. package/coverage/storybook/lcov-report/List.tsx.html +0 -364
  54. package/coverage/storybook/lcov-report/Modal.tsx.html +0 -745
  55. package/coverage/storybook/lcov-report/Pill.tsx.html +0 -358
  56. package/coverage/storybook/lcov-report/Search.tsx.html +0 -997
  57. package/coverage/storybook/lcov-report/SearchContent.tsx.html +0 -235
  58. package/coverage/storybook/lcov-report/SectionHeader.tsx.html +0 -358
  59. package/coverage/storybook/lcov-report/Select.tsx.html +0 -1012
  60. package/coverage/storybook/lcov-report/Shield.tsx.html +0 -802
  61. package/coverage/storybook/lcov-report/SideBarNav.tsx.html +0 -490
  62. package/coverage/storybook/lcov-report/Skeleton.tsx.html +0 -394
  63. package/coverage/storybook/lcov-report/Slider.tsx.html +0 -385
  64. package/coverage/storybook/lcov-report/Status.tsx.html +0 -322
  65. package/coverage/storybook/lcov-report/Tabs.tsx.html +0 -610
  66. package/coverage/storybook/lcov-report/Toggle.tsx.html +0 -373
  67. package/coverage/storybook/lcov-report/Tooltip.tsx.html +0 -496
  68. package/coverage/storybook/lcov-report/base.css +0 -224
  69. package/coverage/storybook/lcov-report/block-navigation.js +0 -87
  70. package/coverage/storybook/lcov-report/favicon.png +0 -0
  71. package/coverage/storybook/lcov-report/index.html +0 -476
  72. package/coverage/storybook/lcov-report/prettify.css +0 -1
  73. package/coverage/storybook/lcov-report/prettify.js +0 -2
  74. package/coverage/storybook/lcov-report/sort-arrow-sprite.png +0 -0
  75. package/coverage/storybook/lcov-report/sorter.js +0 -196
  76. package/coverage/storybook/lcov.info +0 -2312
  77. package/dist/README.md +0 -1815
  78. package/eslint.config.mjs +0 -13
  79. package/project.json +0 -11
  80. package/src/assets/img/Frame.svg +0 -5
  81. package/src/assets/img/backArrowRight.svg +0 -10
  82. package/src/assets/img/bc-separator.png +0 -0
  83. package/src/assets/img/calendar.png +0 -0
  84. package/src/assets/img/calendar.svg +0 -4
  85. package/src/assets/img/check.svg +0 -5
  86. package/src/assets/img/check_box.svg +0 -10
  87. package/src/assets/img/check_box_empty.svg +0 -10
  88. package/src/assets/img/check_box_fill.svg +0 -10
  89. package/src/assets/img/check_box_fill_empty.svg +0 -10
  90. package/src/assets/img/chevron-down-white.svg +0 -2
  91. package/src/assets/img/chevron-down.svg +0 -2
  92. package/src/assets/img/chevron-left.svg +0 -1
  93. package/src/assets/img/chevron-right-light.svg +0 -4
  94. package/src/assets/img/chevron-right.svg +0 -3
  95. package/src/assets/img/chevron-up-white.svg +0 -1
  96. package/src/assets/img/chevron-up.svg +0 -1
  97. package/src/assets/img/clock.svg +0 -6
  98. package/src/assets/img/close.svg +0 -1
  99. package/src/assets/img/close2.svg +0 -6
  100. package/src/assets/img/closeModal.svg +0 -10
  101. package/src/assets/img/close_icon_dark.svg +0 -10
  102. package/src/assets/img/close_small.svg +0 -3
  103. package/src/assets/img/emergency_home.svg +0 -10
  104. package/src/assets/img/first-aid-kit.svg +0 -7
  105. package/src/assets/img/heartbeat.svg +0 -4
  106. package/src/assets/img/home-gray.svg +0 -3
  107. package/src/assets/img/home.svg +0 -3
  108. package/src/assets/img/hospital.jpg +0 -0
  109. package/src/assets/img/indeterminate_check_box.svg +0 -10
  110. package/src/assets/img/indeterminate_check_box_fill.svg +0 -10
  111. package/src/assets/img/info_24_ 1d4ed8.svg +0 -3
  112. package/src/assets/img/info_24_ 2c6441.svg +0 -3
  113. package/src/assets/img/marker_check_by_default.svg +0 -10
  114. package/src/assets/img/marker_check_by_default_fill.svg +0 -10
  115. package/src/assets/img/minus-accordion.svg +0 -5
  116. package/src/assets/img/minus.svg +0 -3
  117. package/src/assets/img/open.svg +0 -1
  118. package/src/assets/img/pill-white.svg +0 -7
  119. package/src/assets/img/pill.svg +0 -5
  120. package/src/assets/img/plus-accordion.svg +0 -5
  121. package/src/assets/img/plus.svg +0 -4
  122. package/src/assets/img/prescription.svg +0 -6
  123. package/src/assets/img/search.svg +0 -10
  124. package/src/assets/img/search_icon_light.svg +0 -10
  125. package/src/assets/img/separator.svg +0 -3
  126. package/src/assets/img/stethoscope-white.svg +0 -8
  127. package/src/assets/img/stethoscope.svg +0 -8
  128. package/src/assets/img/thumb_up.svg +0 -10
  129. package/src/assets/img/vector.svg +0 -3
  130. package/src/assets/img/warning-badge-disabled.svg +0 -11
  131. package/src/assets/img/warning-badge-green.svg +0 -11
  132. package/src/assets/img/warning-badge-red.svg +0 -11
  133. package/src/assets/img/warning-badge-yellow.svg +0 -11
  134. package/src/assets/img/warning.svg +0 -10
  135. package/src/global.d.ts +0 -13
  136. package/src/lib/Accordian--Accordian.stories.tsx +0 -312
  137. package/src/lib/Accordion.spec.tsx +0 -384
  138. package/src/lib/Accordion.tsx +0 -240
  139. package/src/lib/AppointmentPicker.spec.tsx +0 -138
  140. package/src/lib/AppointmentPicker.tsx +0 -97
  141. package/src/lib/Badge--Badge.stories.tsx +0 -60
  142. package/src/lib/Badge.spec.tsx +0 -70
  143. package/src/lib/Badge.tsx +0 -87
  144. package/src/lib/Breadcrumbs-Breadcrumbs.stories.tsx +0 -114
  145. package/src/lib/Breadcrumbs.spec.tsx +0 -218
  146. package/src/lib/Breadcrumbs.tsx +0 -219
  147. package/src/lib/Button--Button.stories.tsx +0 -220
  148. package/src/lib/Button.spec.tsx +0 -241
  149. package/src/lib/Button.tsx +0 -121
  150. package/src/lib/ButtonGroup--ButtonGroup.stories.tsx +0 -129
  151. package/src/lib/ButtonGroup.spec.tsx +0 -89
  152. package/src/lib/ButtonGroup.tsx +0 -107
  153. package/src/lib/Card--Card.stories.tsx +0 -113
  154. package/src/lib/Card.spec.tsx +0 -112
  155. package/src/lib/Card.tsx +0 -69
  156. package/src/lib/CharacterCounter--CharacterCounter.stories.tsx +0 -169
  157. package/src/lib/CharacterCounter.spec.tsx +0 -123
  158. package/src/lib/CharacterCounter.tsx +0 -56
  159. package/src/lib/CheckBox--CheckBox.stories.tsx +0 -107
  160. package/src/lib/CheckBox.spec.tsx +0 -412
  161. package/src/lib/CheckBox.tsx +0 -491
  162. package/src/lib/DatePicker--DatePicker.stories.tsx +0 -228
  163. package/src/lib/DatePicker.spec.tsx +0 -424
  164. package/src/lib/DatePicker.tsx +0 -247
  165. package/src/lib/Input--Input.stories.tsx +0 -449
  166. package/src/lib/Input.spec.tsx +0 -281
  167. package/src/lib/Input.tsx +0 -309
  168. package/src/lib/List--List.stories.tsx +0 -157
  169. package/src/lib/List.spec.tsx +0 -211
  170. package/src/lib/List.tsx +0 -93
  171. package/src/lib/Modal--Modal.stories.tsx +0 -454
  172. package/src/lib/Modal.spec.tsx +0 -202
  173. package/src/lib/Modal.tsx +0 -220
  174. package/src/lib/Pill--Pill.stories.tsx +0 -98
  175. package/src/lib/Pill.spec.tsx +0 -103
  176. package/src/lib/Pill.tsx +0 -91
  177. package/src/lib/ProgressBar.spec.tsx +0 -106
  178. package/src/lib/ProgressBar.tsx +0 -112
  179. package/src/lib/RadioGroup.spec.tsx +0 -84
  180. package/src/lib/RadioGroup.tsx +0 -74
  181. package/src/lib/RadioIcon.tsx +0 -13
  182. package/src/lib/Search--Search.stories.tsx +0 -67
  183. package/src/lib/Search.spec.tsx +0 -182
  184. package/src/lib/Search.tsx +0 -304
  185. package/src/lib/SearchContent.tsx +0 -51
  186. package/src/lib/SectionHeader--SectionHeader.stories.tsx +0 -98
  187. package/src/lib/SectionHeader.spec.tsx +0 -60
  188. package/src/lib/SectionHeader.tsx +0 -91
  189. package/src/lib/Select--Select.stories.tsx +0 -387
  190. package/src/lib/Select.spec.tsx +0 -493
  191. package/src/lib/Select.tsx +0 -311
  192. package/src/lib/Shield--Shield.stories.tsx +0 -196
  193. package/src/lib/Shield.spec.tsx +0 -275
  194. package/src/lib/Shield.tsx +0 -239
  195. package/src/lib/SideBarNav--SideBarNav.stories.tsx +0 -136
  196. package/src/lib/SideBarNav.spec.tsx +0 -178
  197. package/src/lib/SideBarNav.tsx +0 -135
  198. package/src/lib/Skeleton--Skeleton.stories.tsx +0 -77
  199. package/src/lib/Skeleton.module.css +0 -16
  200. package/src/lib/Skeleton.spec.tsx +0 -83
  201. package/src/lib/Skeleton.tsx +0 -103
  202. package/src/lib/SkipLink.spec.tsx +0 -76
  203. package/src/lib/SkipLink.tsx +0 -48
  204. package/src/lib/Slider--Slider.stories.tsx +0 -108
  205. package/src/lib/Slider.module.css +0 -109
  206. package/src/lib/Slider.spec.tsx +0 -67
  207. package/src/lib/Slider.tsx +0 -101
  208. package/src/lib/Status--Status.stories.tsx +0 -93
  209. package/src/lib/Status.spec.tsx +0 -118
  210. package/src/lib/Status.tsx +0 -79
  211. package/src/lib/Tabs--Tabs.stories.tsx +0 -294
  212. package/src/lib/Tabs.spec.tsx +0 -249
  213. package/src/lib/Tabs.tsx +0 -188
  214. package/src/lib/Tester.spec.tsx +0 -17
  215. package/src/lib/Toggle--Toggle.stories.tsx +0 -162
  216. package/src/lib/Toggle.spec.tsx +0 -122
  217. package/src/lib/Toggle.tsx +0 -96
  218. package/src/lib/Tooltip--Tooltip.stories.tsx +0 -315
  219. package/src/lib/Tooltip.spec.tsx +0 -307
  220. package/src/lib/Tooltip.tsx +0 -137
  221. package/src/lib/bak-simple-ui.stories.tsx-bak +0 -24
  222. package/src/styles.css +0 -190
  223. package/tsconfig.json +0 -25
  224. package/tsconfig.lib.json +0 -42
  225. package/tsconfig.spec.json +0 -29
  226. package/tsconfig.storybook.json +0 -36
  227. package/vite.config.mts +0 -87
  228. package/vitest.setup.ts +0 -12
@@ -1,218 +0,0 @@
1
- import { render, screen, act } from "@testing-library/react";
2
- import { describe, it, expect } from "vitest";
3
- import { axe } from "vitest-axe";
4
- import { MemoryRouter } from "react-router-dom";
5
- import { Breadcrumbs } from "./Breadcrumbs";
6
- //import vector from '../assets/img/vector.svg';
7
-
8
- // Mock the ResizeObserver
9
- const ResizeObserverMock = vi.fn(() => ({
10
- observe: vi.fn(),
11
- unobserve: vi.fn(),
12
- disconnect: vi.fn(),
13
- }));
14
-
15
- // Stub the global ResizeObserver
16
- vi.stubGlobal('ResizeObserver', ResizeObserverMock);
17
-
18
- describe("Breadcrumbs Component", () => {
19
- it("renders the breadcrumbs with the correct route structure", () => {
20
- render(
21
- <MemoryRouter initialEntries={["/home/about/contact"]}>
22
- <Breadcrumbs />
23
- </MemoryRouter>
24
- );
25
-
26
- expect(screen.getByText("Home")).toBeInTheDocument();
27
- expect(screen.getByText("About")).toBeInTheDocument();
28
- expect(screen.getByText("Contact")).toBeInTheDocument();
29
- });
30
-
31
- it("renders the last breadcrumb as plain text", () => {
32
- render(
33
- <MemoryRouter initialEntries={["/home/about/contact"]}>
34
- <Breadcrumbs />
35
- </MemoryRouter>
36
- );
37
-
38
- const contact = screen.getByText("Contact");
39
- expect(contact).not.toHaveAttribute("href");
40
- expect(contact).toHaveAttribute("aria-current", "page");
41
- });
42
-
43
- it("renders intermediate breadcrumbs as links", () => {
44
- render(
45
- <MemoryRouter initialEntries={["/home/about/contact"]}>
46
- <Breadcrumbs />
47
- </MemoryRouter>
48
- );
49
-
50
- const home = screen.getByText("Home");
51
- const about = screen.getByText("About");
52
-
53
- expect(home).toHaveAttribute("href", "/home");
54
- expect(about).toHaveAttribute("href", "/home/about");
55
- });
56
-
57
- // if coverage suffers, fix this test
58
- // it("uses the provided separator", () => {
59
- // render(
60
- // <MemoryRouter initialEntries={["/home/about/contact"]}>
61
- // <Breadcrumbs separator={vector} />
62
- // </MemoryRouter>
63
- // );
64
-
65
- // expect(screen.getAllByAltText("vector").length).toBe(2); // Ensure separator is rendered between breadcrumbs
66
- // });
67
-
68
- it("applies custom classes to the container and links", () => {
69
- render(
70
- <MemoryRouter initialEntries={["/home/about/contact"]}>
71
- <Breadcrumbs
72
- classNameContainer="custom-container"
73
- className="custom-link"
74
- />
75
- </MemoryRouter>
76
- );
77
-
78
- expect(screen.getByLabelText("Breadcrumbs")).toHaveClass("custom-container");
79
- const links = screen.getAllByRole("link");
80
- links.forEach((link) => expect(link).toHaveClass("custom-link"));
81
- });
82
-
83
- it("has no accessibility violations", async () => {
84
- const { container } = render(
85
- <MemoryRouter initialEntries={["/home/about/contact"]}>
86
- <Breadcrumbs />
87
- </MemoryRouter>
88
- );
89
-
90
- const results = await axe(container);
91
- expect(results).toHaveNoViolations();
92
- });
93
-
94
- it("handles routes with hyphens and decodes them", () => {
95
- render(
96
- <MemoryRouter initialEntries={["/home/about-page/contact-us"]}>
97
- <Breadcrumbs />
98
- </MemoryRouter>
99
- );
100
-
101
- expect(screen.getByText("About Page")).toBeInTheDocument();
102
- expect(screen.getByText("Contact Us")).toBeInTheDocument();
103
- });
104
-
105
- // it("renders no breadcrumbs if the path is root ('/')", () => {
106
- // render(
107
- // <MemoryRouter initialEntries={["/"]}>
108
- // <Breadcrumbs />
109
- // </MemoryRouter>
110
- // );
111
-
112
- // expect(screen.queryByRole("listitem")).not.toBeInTheDocument();
113
- // });
114
-
115
- it("verifies manually specified routes exist", () => {
116
- render(
117
- <MemoryRouter >
118
- <Breadcrumbs routes={[{ name: 'Main', route: '/' }, { name: 'Tools', route: '/tools' }]}/>
119
- </MemoryRouter>
120
- );
121
-
122
- expect(screen.getByText("Main")).toBeInTheDocument();
123
- expect(screen.getByText("Tools")).toBeInTheDocument();
124
- });
125
- });
126
-
127
- describe("Breadcrumbs — overflow → ellipsis", () => {
128
- beforeEach(() => {
129
- // Mock measurements so that scrollWidth > clientWidth → triggers ellipsis
130
- Object.defineProperty(HTMLElement.prototype, "clientWidth", {
131
- configurable: true,
132
- get() {
133
- return 100;
134
- },
135
- });
136
- Object.defineProperty(HTMLElement.prototype, "scrollWidth", {
137
- configurable: true,
138
- get() {
139
- return 200;
140
- },
141
- });
142
- Object.defineProperty(HTMLElement.prototype, "offsetWidth", {
143
- configurable: true,
144
- get() {
145
- return 100;
146
- },
147
- });
148
-
149
- // Simple ResizeObserver stub that immediately invokes the callback
150
- class MockRO {
151
- callback: ResizeObserverCallback;
152
- constructor(cb: ResizeObserverCallback) {
153
- this.callback = cb;
154
- }
155
- observe() {
156
- // call synchronously so ellipsis state flips immediately
157
- this.callback(
158
- [
159
- {
160
- target: document.createElement("div"),
161
- contentRect: {} as DOMRectReadOnly,
162
- } as unknown as ResizeObserverEntry,
163
- ],
164
- this
165
- );
166
- }
167
- // eslint-disable-next-line @typescript-eslint/no-empty-function
168
- unobserve() {}
169
- // eslint-disable-next-line @typescript-eslint/no-empty-function
170
- disconnect() {}
171
- }
172
- // @ts-expect-error
173
- global.ResizeObserver = MockRO;
174
- });
175
-
176
- afterEach(() => {
177
- // clean up our globals
178
- delete (HTMLElement.prototype as any).clientWidth;
179
- delete (HTMLElement.prototype as any).scrollWidth;
180
- delete (HTMLElement.prototype as any).offsetWidth;
181
- // @ts-ignore
182
- delete global.ResizeObserver;
183
- });
184
-
185
- it("renders only the final crumb with “...” when overflowed", async () => {
186
- // wrap in act so that useEffect and ResizeObserver fire
187
- await act(async () => {
188
- render(
189
- <MemoryRouter initialEntries={["/first/second"]}>
190
- <Breadcrumbs />
191
- </MemoryRouter>
192
- );
193
- });
194
-
195
- // “...” placeholder should be present
196
- expect(screen.getByText("...")).toBeInTheDocument();
197
-
198
- // final segment (“Second”) should render as plain text with aria-current
199
- const lastCrumb = screen.getByText("Second");
200
- expect(lastCrumb).toBeInTheDocument();
201
- expect(lastCrumb).toHaveAttribute("aria-current", "page");
202
-
203
- });
204
-
205
- it("has no detectable accessibility violations", async () => {
206
- let container: HTMLElement;
207
- await act(async () => {
208
- const rendered = render(
209
- <MemoryRouter initialEntries={["/a/b/c"]}>
210
- <Breadcrumbs />
211
- </MemoryRouter>
212
- );
213
- container = rendered.container;
214
- });
215
- const results = await axe(container!);
216
- expect(results).toHaveNoViolations();
217
- });
218
- });
@@ -1,219 +0,0 @@
1
- import * as React from 'react';
2
- import { twMerge } from 'tailwind-merge';
3
- import { useLocation, Link } from 'react-router-dom';
4
- import separatorSvg from '../assets/img/separator.svg';
5
- import home from '../assets/img/home-gray.svg';
6
- import { useEffect, useRef, useState, useMemo } from 'react';
7
-
8
- function prettify(name: string) {
9
- return name
10
- .replace(/-/g, ' ')
11
- .replace(/\b\w/g, c => c.toUpperCase());
12
- }
13
- interface VariantType {
14
- [key: string]: string;
15
- }
16
-
17
- // variant styling definition, both size and layout (portrait vs landscape)
18
- const variants: VariantType = {
19
- default: 'overflow-x-auto w-full text-base md:text-lg', // image top
20
- bold: 'overflow-x-auto font-bold w-full text-base md:text-lg',
21
- }
22
-
23
- export interface route {
24
- name: string;
25
- route: string;
26
- }
27
-
28
- export interface BreadcrumbsProps extends React.HTMLAttributes<HTMLDivElement> {
29
- className?: string;
30
- classNameContainer?: string;
31
- variant?: string;
32
- separator?: string; // i.e. >>, or -->, etc.
33
- routes?: route[];
34
- }
35
-
36
- export const Breadcrumbs = React.forwardRef<HTMLDivElement, BreadcrumbsProps>(
37
- ({ classNameContainer, className, routes, variant = 'default',
38
- separator = separatorSvg, ...props }, ref) => {
39
- const containerRef = useRef<HTMLDivElement>(null);
40
- const iconRef = useRef<HTMLImageElement>(null);
41
- const [ellipsis, setEllipsis] = useState(false);
42
- const [currentTime, setCurrentTime] = useState<number>();
43
- const [reqContentWidth, setReqContentWidth] = useState<number>(0); // content width required to display non-ellipsis breadcrumbs
44
-
45
- // Define Routes
46
- const { pathname } = useLocation();
47
-
48
- const finalRoutes: route[] = useMemo(() => {
49
- // if manualRoutes was passed in, use it
50
- if (routes && routes.length > 0) {
51
- return routes;
52
- }
53
-
54
- const segments = pathname
55
- .split('/')
56
- .filter(Boolean);
57
-
58
- return segments.map((segment, idx, arr) => ({
59
- name: prettify(decodeURIComponent(segment)),
60
- route: '/' + arr.slice(0, idx + 1).join('/'),
61
- }));
62
- }, [routes, pathname]);
63
-
64
-
65
- // measures containerWidth at page load
66
- // should re-measure at home-icon swap
67
- useEffect(() => {
68
- const fullContainer = containerRef.current;
69
- if (fullContainer) {
70
- setReqContentWidth(fullContainer.offsetWidth);
71
- }
72
-
73
- // build auto-routes if manualRoutes not present
74
- }, []);
75
-
76
- const checkOverflow = () => {
77
- const container = containerRef.current;
78
-
79
- if ((container && !currentTime) || (container && currentTime && Date.now() > (currentTime + 200))) {
80
-
81
- if (ellipsis) {
82
- if (container.clientWidth > reqContentWidth) {
83
- // console.log('setting ellipsis false');
84
- setEllipsis(false);
85
- }
86
- } else {
87
- if (container.scrollWidth > container.clientWidth) {
88
- // console.log('setting ellipsis true');
89
- setEllipsis(true);
90
- }
91
- }
92
-
93
- setCurrentTime(Date.now());
94
- }
95
- };
96
-
97
- useEffect(() => {
98
- // clientWidth = 0 for inline or w/o css, otherwise width of inner element in pixels
99
- // scrollWidth = measurement of width of elements content
100
- // console.log('breadcrumbs useEffect');
101
-
102
- const container = containerRef.current;
103
- if (!container) {
104
- // console.log('no container - returning');
105
- return;
106
- }
107
-
108
- // run once to set initial state
109
- checkOverflow();
110
-
111
- // whenever the container itself changes size, re-check
112
- const ro = new ResizeObserver(checkOverflow);
113
- ro.observe(container);
114
- return () => { ro.disconnect(); };
115
- // eslint-disable-next-line react-hooks/exhaustive-deps
116
- }, [currentTime, finalRoutes]); // re‑run when the crumbs change
117
-
118
- return (
119
- <nav
120
- className={twMerge(variants[variant], classNameContainer)}
121
- {...props}
122
- ref={ref}
123
- aria-label="Breadcrumbs"
124
- >
125
- <div
126
- ref={containerRef}
127
- className="flex list-none flex-nowrap text-clip overflow-hidden text-nowrap w-full"
128
- >
129
- {/* First item / home page link */}
130
- <span className="breadcrumb-item inline-flex items-center shrink-0">
131
- <Link
132
- to="/"
133
- className={twMerge(
134
- "text-[#747476] hover:text-[#0000ff] md:hover:underline capitalize me-2 hidden md:inline-flex",
135
- className
136
- )}>Homepage
137
- </Link>
138
- <Link
139
- to="/"
140
- className={twMerge(
141
- "capitalize me-2 inline-flex md:hidden",
142
- className
143
- )}><img src={home} ref={iconRef} alt='home icon'></img>
144
- </Link>
145
- {finalRoutes.length > 0 ? <span className={twMerge("me-2", className)}><img src={separator} alt='vector'></img></span> : ''}
146
- </span>
147
-
148
- <span
149
- className=''
150
- // ref={ellipsisRef}
151
- // ellipsisRef is used to measure size of container w/ content - may have to move to a different span
152
- >
153
- {finalRoutes.map((part, index) => {
154
- // Reconstruct the path up to the current segment
155
- // const path = '/' + routes.slice(0, index + 1).join('/');
156
-
157
- if (ellipsis) {
158
- return (
159
- <span key={index} className='inline-flex items-center shrink-0 text-nowrap'>
160
- {index === finalRoutes.length - 1 ? (
161
- <>
162
- <span className='text-nowrap me-3'>...</span>
163
- <span className={twMerge("me-3", className)}><img src={separator} alt='vector'></img></span>
164
- <span
165
- className="text-black font-bold capitalize text-nowrap"
166
- aria-current="page"
167
- >
168
- {decodeURIComponent(part.name).replace(/-/g, ' ')}
169
- </span>
170
- </>
171
- ) : (
172
- ''
173
- )}
174
- </span>
175
- );
176
- } else {
177
-
178
- return (
179
- <span
180
- key={index}
181
- className="breadcrumb-item inline-flex items-center shrink-0"
182
- >
183
-
184
- {index === finalRoutes.length - 1 ? (
185
- // Render the current page as plain text (last item in list)
186
- <span
187
- className="text-black font-bold capitalize"
188
- aria-current="page"
189
- >
190
- {decodeURIComponent(part.name).replace(/-/g, ' ')}
191
- </span>
192
- ) : (
193
- // Render intermediate items as links
194
- <Link
195
- to={part.route}
196
- className={twMerge(
197
- "text-[#747476] hover:text-[#0000ff] md:hover:underline capitalize me-3",
198
- className
199
- )}
200
- >
201
- {decodeURIComponent(part.name).replace(/-/g, ' ')}
202
- </Link>
203
- )}
204
- {/* Separator for intermediate items */}
205
- {index < finalRoutes.length - 1 && (
206
- <span className={twMerge("me-3", className)}><img src={separator} alt='vector'></img></span>
207
- )}
208
- </span>
209
- );
210
-
211
- }
212
-
213
- })}
214
- </span>
215
- </div>
216
- </nav>
217
- );
218
- }
219
- );
@@ -1,220 +0,0 @@
1
- import { Meta, StoryContext } from '@storybook/react';
2
- import { Button, ButtonProps } from './Button';
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/Button',
9
- component: Button,
10
- argTypes: {
11
- variant: {
12
- control: 'select',
13
- options: ['default', 'filled', 'outline', 'transparent']
14
- },
15
- },
16
- args: {
17
- disabled: false, // set default argument values
18
- // label: 'Button', // set default argument values
19
- },
20
- parameters: {
21
- layout: 'centered', // options are 'centered', 'fullscreen', and 'padded' (default value)
22
- backgrounds: { default: 'dark' }, // options are light, medium, or dark
23
- },
24
- } as Meta<ButtonProps>;
25
-
26
- // Define "Default" story
27
- export const Default = {
28
- args: {
29
- children: 'Button',
30
- variant: 'default',
31
- size: 'default',
32
- }
33
- };
34
-
35
- export const LeftIcon = {
36
- args: {
37
- label: 'Button with left icon',
38
- // Pass an icon element with a test ID so we can find it.
39
- icon: <svg data-testid="left-icon" />,
40
- iconPosition: 'left',
41
- },
42
- play: async ({ canvasElement }: StoryContext) => {
43
- const canvas = within(canvasElement);
44
- const button = canvas.getByRole('button', { name: /button with left icon/i });
45
-
46
- // Line 87–89: There should be a span with class "icon-left"
47
- const leftIconSpan = button.querySelector('.icon-left');
48
- expect(leftIconSpan).toBeInTheDocument();
49
-
50
- // Verify the icon element is rendered inside that span.
51
- const iconElement = leftIconSpan?.querySelector('[data-testid="left-icon"]');
52
- expect(iconElement).toBeInTheDocument();
53
-
54
- // Also, because iconPosition is "left", the text is rendered in a span with class "button-text".
55
- const textSpan = button.querySelector('.button-text');
56
- expect(textSpan).toHaveTextContent('Button with left icon');
57
- },
58
- };
59
-
60
- export const IconOnly = {
61
- args: {
62
- label: 'Icon Only', // This label will be ignored in rendering because an icon is provided.
63
- icon: <svg data-testid="icon-only" />,
64
- iconPosition: 'iconOnly',
65
- },
66
- play: async ({ canvasElement }: StoryContext) => {
67
- const canvas = within(canvasElement);
68
- const button = canvas.getByRole('button');
69
-
70
- // The conditional (lines 91–98) should render only the icon.
71
- // There should be no span with class "button-text"
72
- const textSpan = button.querySelector('.button-text');
73
- expect(textSpan).toBeNull();
74
-
75
- // Instead, the icon is wrapped in a span with class "size-6".
76
- const iconSpan = button.querySelector('.size-6');
77
- expect(iconSpan).toBeInTheDocument();
78
- const iconElement = iconSpan?.querySelector('[data-testid="icon-only"]');
79
- expect(iconElement).toBeInTheDocument();
80
- },
81
- };
82
-
83
- export const RightIcon = {
84
- args: {
85
- label: 'Button with right icon',
86
- icon: <svg data-testid="right-icon" />,
87
- iconPosition: 'right',
88
- },
89
- play: async ({ canvasElement }: StoryContext) => {
90
- const canvas = within(canvasElement);
91
- const button = canvas.getByRole('button', { name: /button with right icon/i });
92
-
93
- // The text should be rendered (via the else branch of the conditional)...
94
- const textSpan = button.querySelector('.button-text');
95
- expect(textSpan).toHaveTextContent('Button with right icon');
96
-
97
- // ...and line 101 should cause a span with class "icon-right" to render.
98
- const rightIconSpan = button.querySelector('.icon-right');
99
- expect(rightIconSpan).toBeInTheDocument();
100
-
101
- // Confirm that the icon element is inside the right icon span.
102
- const iconElement = rightIconSpan?.querySelector('[data-testid="right-icon"]');
103
- expect(iconElement).toBeInTheDocument();
104
- },
105
- };
106
-
107
- // Define "Blank" story
108
- export const Filled = {
109
- args: {
110
- children: 'Button',
111
- variant: 'filled',
112
- size: 'default',
113
- }
114
- };
115
-
116
- // Define "Blank" story
117
- export const Outline = {
118
- args: {
119
- children: 'Button',
120
- variant: 'outline',
121
- size: 'default',
122
- }
123
- };
124
-
125
- // Define "Blank" story
126
- export const Transparent = {
127
- args: {
128
- children: 'Button',
129
- variant: 'transparent',
130
- size: 'default',
131
- }
132
- };
133
-
134
- export const SelectedDefault = {
135
- args: {
136
- label: 'Selected Default',
137
- selected: true,
138
- variant: 'default',
139
- // Do not pass classNameSelected so that line 69 executes
140
- },
141
- play: async ({ canvasElement }: StoryContext) => {
142
- const canvas = within(canvasElement);
143
- // Get the button by its role and accessible name.
144
- const button = canvas.getByRole('button', { name: /selected default/i });
145
-
146
- // For the "default" variant, the selected state (when no custom classNameSelected are provided)
147
- // appends "bg-gray-500 text-white" (see variants.default.selected).
148
- await expect(button.className).toContain('bg-gray-500');
149
- await expect(button.className).toContain('text-white');
150
- },
151
- };
152
-
153
- export const SelectedCustom = {
154
- args: {
155
- label: 'Selected Custom',
156
- selected: true,
157
- variant: 'default',
158
- classNameSelected: 'bg-custom text-custom', // This custom value should be merged on line 71
159
- },
160
- play: async ({ canvasElement }: StoryContext) => {
161
- const canvas = within(canvasElement);
162
- const button = canvas.getByRole('button', { name: /selected custom/i });
163
-
164
- // Check that the custom selected classes are present in the className.
165
- await expect(button.className).toContain('bg-custom');
166
- await expect(button.className).toContain('text-custom');
167
- },
168
- };
169
-
170
- // Define "Blank" story
171
- export const OutlineWithCustomClass = {
172
- args: {
173
- children: 'Button',
174
- variant: 'outline',
175
- size: 'default',
176
- className: 'text-purple-600 hover:text-purple-700',
177
- }
178
- };
179
-
180
- // Define "DHA Disabled" story
181
- export const DHADisabled = {
182
- args: {
183
- disabled: true,
184
- label: 'Disabled Button',
185
- }
186
- };
187
-
188
-
189
- // Define "Alternate Classes" story
190
- export const AlternateClasses = {
191
- args: {
192
- children: 'Custom Classes',
193
- onClick: () => console.log('Clicked!'),
194
- className: 'border-8 border-black text-white bg-orange-500',
195
- }
196
- };
197
-
198
- // Define a story using an alternate method that contains state values
199
- export const ButtonWithClickHandler = ({ disabled, ...rest }: ButtonProps) => {
200
- // const [clicked, setClicked] = useState(false);
201
- // const handleClick = () => setClicked(!clicked);
202
- const [variant, setVariant] = useState<string>('filled');
203
-
204
- const handleClick = (() => {
205
- if (variant === 'filled')
206
- setVariant('outline');
207
- else
208
- setVariant('filled');
209
- })
210
-
211
- return (
212
- <Button
213
- onClick={handleClick}
214
- variant={variant}
215
- disabled={disabled}
216
- >
217
- Click Me! I am {variant}.
218
- </Button>
219
- );
220
- }