@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,241 @@
|
|
|
1
|
+
import React, { createRef } from "react";
|
|
2
|
+
import { render, screen, fireEvent } from "@testing-library/react";
|
|
3
|
+
import { Button, ButtonProps } from "./Button";
|
|
4
|
+
import { axe } from "vitest-axe";
|
|
5
|
+
|
|
6
|
+
describe("Button Component", () => {
|
|
7
|
+
it("renders the button with default styles", () => {
|
|
8
|
+
render(<Button>Default Button</Button>);
|
|
9
|
+
const button = screen.getByRole("button", { name: "Default Button" });
|
|
10
|
+
|
|
11
|
+
expect(button).toBeInTheDocument();
|
|
12
|
+
expect(button).toHaveClass("inline-flex items-center justify-center whitespace-nowrap");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("applies custom classes and merges with default classes", () => {
|
|
16
|
+
render(<Button className="custom-class">Custom Class Button</Button>);
|
|
17
|
+
const button = screen.getByRole("button", { name: "Custom Class Button" });
|
|
18
|
+
|
|
19
|
+
expect(button).toHaveClass("custom-class");
|
|
20
|
+
expect(button).toHaveClass("inline-flex items-center justify-center whitespace-nowrap");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/*it("applies variant-specific styles", () => {
|
|
24
|
+
render(<Button variant="Outline">Outline Button</Button>);
|
|
25
|
+
const button = screen.getByRole("button", { name: "Outline Button" });
|
|
26
|
+
|
|
27
|
+
expect(button).toHaveClass("border-dha-mc-true-blue bg-white border-2 text-dha-mc-true-blue");
|
|
28
|
+
});*/
|
|
29
|
+
|
|
30
|
+
it("uses label prop if children are not provided", () => {
|
|
31
|
+
render(<Button label="Label Button" />);
|
|
32
|
+
const button = screen.getByRole("button", { name: "Label Button" });
|
|
33
|
+
|
|
34
|
+
expect(button).toBeInTheDocument();
|
|
35
|
+
expect(button).toHaveTextContent("Label Button");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("prioritizes children over label prop", () => {
|
|
39
|
+
render(
|
|
40
|
+
<Button label="Label Button">Children Button</Button>
|
|
41
|
+
);
|
|
42
|
+
const button = screen.getByRole("button", { name: "Children Button" });
|
|
43
|
+
|
|
44
|
+
expect(button).toHaveTextContent("Children Button");
|
|
45
|
+
expect(button).not.toHaveTextContent("Label Button");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("handles the onClick event", () => {
|
|
49
|
+
const handleClick = vi.fn();
|
|
50
|
+
render(<Button onClick={handleClick}>Clickable Button</Button>);
|
|
51
|
+
const button = screen.getByRole("button", { name: "Clickable Button" });
|
|
52
|
+
|
|
53
|
+
fireEvent.click(button);
|
|
54
|
+
|
|
55
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("sets the correct button type", () => {
|
|
59
|
+
render(<Button type="submit">Submit Button</Button>);
|
|
60
|
+
const button = screen.getByRole("button", { name: "Submit Button" });
|
|
61
|
+
|
|
62
|
+
expect(button).toHaveAttribute("type", "submit");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should forward the ref to the button element", () => {
|
|
66
|
+
const ref = createRef<HTMLButtonElement>();
|
|
67
|
+
render(<Button ref={ref}>Button with Ref</Button>);
|
|
68
|
+
|
|
69
|
+
expect(ref.current).toBeInstanceOf(HTMLButtonElement);
|
|
70
|
+
expect(ref.current?.textContent).toBe("Button with Ref");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("renders a disabled button with appropriate styles", () => {
|
|
74
|
+
render(
|
|
75
|
+
<Button variant="filled" disabled>
|
|
76
|
+
Disabled Button
|
|
77
|
+
</Button>
|
|
78
|
+
);
|
|
79
|
+
const button = screen.getByRole("button", { name: "Disabled Button" });
|
|
80
|
+
|
|
81
|
+
expect(button).toBeDisabled();
|
|
82
|
+
expect(button).toHaveClass("disabled:border-[#e4e4e5]");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
/*
|
|
86
|
+
it("falls back to the default variant if an unknown variant is provided", () => {
|
|
87
|
+
render(
|
|
88
|
+
<Button variant="unknown-variant">
|
|
89
|
+
Fallback Variant Button
|
|
90
|
+
</Button>
|
|
91
|
+
);
|
|
92
|
+
const button = screen.getByRole("button", { name: "Fallback Variant Button" });
|
|
93
|
+
|
|
94
|
+
expect(button).toHaveClass("bg-gray-200 hover:bg-blue-400 text-black");
|
|
95
|
+
});*/
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('Button Accessibility Tests', () => {
|
|
99
|
+
const renderComponent = (props: ButtonProps) =>
|
|
100
|
+
render(<Button {...props} />);
|
|
101
|
+
|
|
102
|
+
it('should have no accessibility violations with default variant', async () => {
|
|
103
|
+
const { container } = renderComponent({ label: 'Click Me' });
|
|
104
|
+
const results = await axe(container);
|
|
105
|
+
expect(results).toHaveNoViolations();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should have no accessibility violations with custom variant', async () => {
|
|
109
|
+
const { container } = renderComponent({ label: 'Submit', variant: 'filled' });
|
|
110
|
+
const results = await axe(container);
|
|
111
|
+
expect(results).toHaveNoViolations();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should support disabled state without accessibility violations', async () => {
|
|
115
|
+
const { container } = renderComponent({ label: 'Disabled', disabled: true });
|
|
116
|
+
const results = await axe(container);
|
|
117
|
+
expect(results).toHaveNoViolations();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should handle custom class merging correctly', async () => {
|
|
121
|
+
const { container } = renderComponent({
|
|
122
|
+
label: 'Custom Class',
|
|
123
|
+
className: 'custom-class bg-red-500 text-white',
|
|
124
|
+
});
|
|
125
|
+
const results = await axe(container);
|
|
126
|
+
expect(results).toHaveNoViolations();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should render a button with accessible name using label', () => {
|
|
130
|
+
const { getByRole } = renderComponent({ label: 'Accessible Button' });
|
|
131
|
+
const button = getByRole('button', { name: 'Accessible Button' });
|
|
132
|
+
expect(button).toBeInTheDocument();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should render children and fallback to label if children are not provided', () => {
|
|
136
|
+
const { getByText, rerender } = renderComponent({ label: 'Fallback Label' });
|
|
137
|
+
expect(getByText('Fallback Label')).toBeInTheDocument();
|
|
138
|
+
|
|
139
|
+
rerender(<Button>Child Content</Button>);
|
|
140
|
+
expect(getByText('Child Content')).toBeInTheDocument();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should handle ARIA attributes for assistive technologies', () => {
|
|
144
|
+
const { getByRole } = renderComponent({
|
|
145
|
+
label: 'Button with ARIA',
|
|
146
|
+
'aria-label': 'Custom ARIA Label',
|
|
147
|
+
});
|
|
148
|
+
const button = getByRole('button', { name: 'Custom ARIA Label' });
|
|
149
|
+
expect(button).toBeInTheDocument();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should render a submit button with proper type', () => {
|
|
153
|
+
const { getByRole } = renderComponent({ type: 'submit', label: 'Submit Button' });
|
|
154
|
+
const button = getByRole('button', { name: 'Submit Button' });
|
|
155
|
+
expect(button).toHaveAttribute('type', 'submit');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should handle click events', () => {
|
|
159
|
+
const handleClick = vi.fn();
|
|
160
|
+
const { getByRole } = renderComponent({ label: 'Click Me', onClick: handleClick });
|
|
161
|
+
const button = getByRole('button', { name: 'Click Me' });
|
|
162
|
+
button.click();
|
|
163
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should render with custom styles for hover and active states', async () => {
|
|
167
|
+
const { container } = renderComponent({
|
|
168
|
+
label: 'Hover Button',
|
|
169
|
+
className: 'hover:bg-blue-500 active:bg-blue-700',
|
|
170
|
+
});
|
|
171
|
+
const results = await axe(container);
|
|
172
|
+
expect(results).toHaveNoViolations();
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe("Button Icon and Selected Prop Tests", () => {
|
|
177
|
+
it("applies default variant's selected style when selected is true and no classNameSelected is provided", () => {
|
|
178
|
+
render(<Button label="Selected Button" selected variant="default" />);
|
|
179
|
+
const button = screen.getByRole("button", { name: /Selected Button/ });
|
|
180
|
+
expect(button.className).toContain("bg-gray-500");
|
|
181
|
+
expect(button.className).toContain("text-white");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("applies custom classNameSelected when selected is true", () => {
|
|
185
|
+
render(
|
|
186
|
+
<Button
|
|
187
|
+
label="Custom Selected Button"
|
|
188
|
+
selected
|
|
189
|
+
classNameSelected="selected-custom"
|
|
190
|
+
variant="default"
|
|
191
|
+
/>
|
|
192
|
+
);
|
|
193
|
+
const button = screen.getByRole("button", { name: /Custom Selected Button/ });
|
|
194
|
+
expect(button.className).toContain("selected-custom");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("renders a left icon when iconPosition is 'left'", () => {
|
|
198
|
+
render(
|
|
199
|
+
<Button
|
|
200
|
+
label="Left Icon Button"
|
|
201
|
+
icon={<span data-testid="left-icon">Icon</span>}
|
|
202
|
+
iconPosition="left"
|
|
203
|
+
/>
|
|
204
|
+
);
|
|
205
|
+
const button = screen.getByRole("button", { name: /Left Icon Button/ });
|
|
206
|
+
const leftIcon = screen.getByTestId("left-icon");
|
|
207
|
+
expect(leftIcon).toBeInTheDocument();
|
|
208
|
+
// Check that the icon is wrapped in a span with the proper class.
|
|
209
|
+
expect(leftIcon.parentElement).toHaveClass("icon-left");
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("renders icon only when iconPosition is 'iconOnly'", () => {
|
|
213
|
+
render(
|
|
214
|
+
<Button
|
|
215
|
+
icon={<span data-testid="icon-only">IconOnly</span>}
|
|
216
|
+
iconPosition="iconOnly"
|
|
217
|
+
/>
|
|
218
|
+
);
|
|
219
|
+
const button = screen.getByRole("button");
|
|
220
|
+
const iconOnlySpan = screen.getByTestId("icon-only");
|
|
221
|
+
expect(iconOnlySpan).toBeInTheDocument();
|
|
222
|
+
expect(iconOnlySpan.parentElement).toHaveClass("size-6");
|
|
223
|
+
expect(button.querySelector(".button-text")).toBeNull();
|
|
224
|
+
expect(button.className).toMatch(/px-\[12px\]/);
|
|
225
|
+
expect(button.className).toMatch(/py-\[12px\]/);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("renders a right icon when iconPosition is 'right'", () => {
|
|
229
|
+
render(
|
|
230
|
+
<Button
|
|
231
|
+
label="Right Icon Button"
|
|
232
|
+
icon={<span data-testid="right-icon">Icon</span>}
|
|
233
|
+
iconPosition="right"
|
|
234
|
+
/>
|
|
235
|
+
);
|
|
236
|
+
const button = screen.getByRole("button", { name: /Right Icon Button/ });
|
|
237
|
+
const rightIcon = screen.getByTestId("right-icon");
|
|
238
|
+
expect(rightIcon).toBeInTheDocument();
|
|
239
|
+
expect(rightIcon.parentElement).toHaveClass("icon-right");
|
|
240
|
+
});
|
|
241
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { ButtonHTMLAttributes, forwardRef, ReactNode, useEffect, useState } from "react";
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
|
|
4
|
+
interface VariantType {
|
|
5
|
+
[key: string]: {normal: string, selected: string};
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const baseClasses = 'inline-flex items-center justify-center whitespace-nowrap rounded-md ' +
|
|
9
|
+
'ring-offset-background transition-colors focus-visible:outline-hidden font-["Arial"] ' +
|
|
10
|
+
'disabled:pointer-events-none text-sm md:text-base lg:text-lg ' +
|
|
11
|
+
'disabled:opacity-50 px-6 py-[8px] md:py-[12px] lg:py-[16px] h-[40px] md:h-[48px] lg:h-[56px] ';
|
|
12
|
+
|
|
13
|
+
const variants: VariantType = {
|
|
14
|
+
default: {
|
|
15
|
+
normal: 'border-2 border-gray-300 rounded-md bg-gray-200 hover:bg-slate-400 text-black text-sm md:text-base lg:text-lg hover:text-black ' +
|
|
16
|
+
'hover:border-slate-600 disabled:bg-dha-mc-bottom-nav-background disabled:text-dha-mc-checkbox-inactive ' +
|
|
17
|
+
'focus:border-black ' +
|
|
18
|
+
'disabled:border-dha-mc-bottom-nav-background disabled:border-2 py-0 md:py-0 lg:py-0 h-[48px] mt-1',
|
|
19
|
+
selected: 'bg-gray-500 text-white', // Only used if 'selected' is true to indicate state when 'selected'
|
|
20
|
+
} ,
|
|
21
|
+
filled: {
|
|
22
|
+
normal: 'rounded-md bg-[#092068] hover:bg-[#0c2c8e] text-white text-sm md:text-base lg:text-lg hover:text-white ' +
|
|
23
|
+
'focus:shadow-[0px_0px_0px_3px_rgba(238,131,255,1.00)] active:bg-[#0F37B3] disabled:bg-[#e4e4e5] disabled:text-[#939194] ' +
|
|
24
|
+
'disabled:border-[#e4e4e5] ',
|
|
25
|
+
selected: '', // Only used if 'selected' is true
|
|
26
|
+
},
|
|
27
|
+
outline: {
|
|
28
|
+
normal: 'rounded-md border-[#092068] bg-white border-2 text-[#092068] text-sm md:text-base lg:text-lg disabled:border-dha-mc-secondary-border ' +
|
|
29
|
+
'disabled:text-[#939194] hover:bg-[#d1dbfb] active:bg-[#9fc5f0] ' +
|
|
30
|
+
'focus:shadow-[0px_0px_0px_3px_rgba(238,131,255,1.00)] ',
|
|
31
|
+
selected: '', // Only used if 'selected' is true
|
|
32
|
+
},
|
|
33
|
+
transparent: {
|
|
34
|
+
normal: 'rounded-md text-sm md:text-base lg:text-lg text-[#092068] hover:bg-[#d1dbfb] active:bg-[#9fc5f0] focus:shadow-[0px_0px_0px_3px_rgba(251,137,241,1.00)] ' +
|
|
35
|
+
'disabled:text-[#939194]',
|
|
36
|
+
selected: '', // Only used if 'selected' is true
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
41
|
+
children?: ReactNode;
|
|
42
|
+
label?: string;
|
|
43
|
+
onClick?: () => void;
|
|
44
|
+
className?: string;
|
|
45
|
+
classNameGroup?: string; // not documented on local component - used by ButtonGroup
|
|
46
|
+
icon?: ReactNode | undefined; // Accepts any valid React element (e.g., image, icon component)
|
|
47
|
+
iconPosition?: "left" | "right" | "iconOnly" | undefined;
|
|
48
|
+
variant?: string; // Define allowed variants here, extend as necessary.
|
|
49
|
+
type?: "button" | "submit" | "reset";
|
|
50
|
+
selected?: boolean;
|
|
51
|
+
classNameSelected?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
55
|
+
({ label, onClick, className, icon, iconPosition, variant = "default",
|
|
56
|
+
type = "button", children, selected = false, classNameSelected,
|
|
57
|
+
classNameGroup = '', ...props }, ref) => {
|
|
58
|
+
|
|
59
|
+
const [mergedClasses, setMergedClasses] = useState('');
|
|
60
|
+
// console.log('iconPosition: ', iconPosition);
|
|
61
|
+
|
|
62
|
+
// useEffect(() => {
|
|
63
|
+
// const variantClasses = variants[variant] || variants.default;
|
|
64
|
+
// setMergedClasses(twMerge(baseClasses, variantClasses, className));
|
|
65
|
+
// }, [className, variant]);
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
let variantClasses = variants[variant].normal || variants.default.normal;
|
|
69
|
+
|
|
70
|
+
if (selected && !classNameSelected) {
|
|
71
|
+
variantClasses = twMerge(variantClasses, variants[variant].selected);
|
|
72
|
+
} else if (selected && classNameSelected) {
|
|
73
|
+
variantClasses = twMerge(variantClasses, classNameSelected);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
setMergedClasses(twMerge(baseClasses, variantClasses))
|
|
77
|
+
|
|
78
|
+
}, [selected, classNameSelected, variant]);
|
|
79
|
+
|
|
80
|
+
// Button contains default classes, classNameGroup, and className
|
|
81
|
+
// we assign classes from ButtonGroup to classNameGroup
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<button
|
|
85
|
+
type={type}
|
|
86
|
+
onClick={onClick}
|
|
87
|
+
className={`${iconPosition === "iconOnly" ?
|
|
88
|
+
(twMerge(
|
|
89
|
+
mergedClasses,
|
|
90
|
+
`px-[12px] md:px-[14px] lg:px-[16px] py-[8px] md:py-[12px] lg:py-[16px]`,
|
|
91
|
+
classNameGroup, // passed in from ButtonGroup if present
|
|
92
|
+
className // dev over-rides
|
|
93
|
+
))
|
|
94
|
+
: twMerge(mergedClasses, classNameGroup, className)}`}
|
|
95
|
+
ref={ref}
|
|
96
|
+
{...props}
|
|
97
|
+
>
|
|
98
|
+
{/* Conditionally render icon on the left or right based on iconPosition */}
|
|
99
|
+
{/* ms/e-3 === 12px */}
|
|
100
|
+
{iconPosition === "left" && icon && (
|
|
101
|
+
<span className="icon-left mr-3 size-6">{icon}</span>
|
|
102
|
+
)}
|
|
103
|
+
{
|
|
104
|
+
iconPosition === "iconOnly" && icon ?
|
|
105
|
+
(
|
|
106
|
+
<span className="size-6">{icon}</span>
|
|
107
|
+
)
|
|
108
|
+
:
|
|
109
|
+
(
|
|
110
|
+
<span className=''>{children ? children : label}</span>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
{iconPosition === "right" && icon && (
|
|
114
|
+
<span className="icon-right ml-3 size-6">{icon}</span>
|
|
115
|
+
)}
|
|
116
|
+
</button>
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Chris suggested for debug component name labeling
|
|
121
|
+
Button.displayName = 'SDK Button'
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Meta, StoryContext, StoryFn } from '@storybook/react';
|
|
2
|
+
import { Button, ButtonProps } from './Button';
|
|
3
|
+
import { ButtonGroup, ButtonGroupProps } from './ButtonGroup';
|
|
4
|
+
import { useState } from 'react';
|
|
5
|
+
import { within, expect, waitFor } from 'storybook/test';
|
|
6
|
+
|
|
7
|
+
// Meta object - defines basic storybook options for this story
|
|
8
|
+
export default {
|
|
9
|
+
title: 'Components/ButtonGroup',
|
|
10
|
+
component: ButtonGroup,
|
|
11
|
+
argTypes: {
|
|
12
|
+
variant: {
|
|
13
|
+
control: 'select',
|
|
14
|
+
options: ['default', 'filled', 'outline', 'transparent']
|
|
15
|
+
},
|
|
16
|
+
size: {
|
|
17
|
+
control: 'select',
|
|
18
|
+
options: ['default', 'sm', 'lg', 'icon'],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
args: {
|
|
22
|
+
disabled: false, // set default argument values
|
|
23
|
+
// label: 'Button', // set default argument values
|
|
24
|
+
},
|
|
25
|
+
parameters: {
|
|
26
|
+
layout: 'centered', // options are 'centered', 'fullscreen', and 'padded' (default value)
|
|
27
|
+
backgrounds: {
|
|
28
|
+
default: 'white',
|
|
29
|
+
values: [
|
|
30
|
+
{ name: 'white', value: '#ffffff' },
|
|
31
|
+
{ name: 'medium', value: '#b5bbb7' },
|
|
32
|
+
{ name: 'dark', value: '#000' },
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
} as Meta<ButtonProps>;
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
export const DefaultOne: StoryFn = () => {
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<ButtonGroup>
|
|
44
|
+
<Button variant='filled'>Primary</Button>
|
|
45
|
+
</ButtonGroup>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
export const DefaultTwo: StoryFn = () => {
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<ButtonGroup>
|
|
54
|
+
<Button variant='outline'>Secondary</Button>
|
|
55
|
+
<Button variant='filled'>Primary</Button>
|
|
56
|
+
</ButtonGroup>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const DefaultThree: StoryFn = () => {
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<ButtonGroup>
|
|
64
|
+
<Button variant='outline'>Secondary</Button>
|
|
65
|
+
<Button variant='filled'>Primary</Button>
|
|
66
|
+
<Button variant='transparent'>Tertiary</Button>
|
|
67
|
+
</ButtonGroup>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
export const ColumnOne: StoryFn = () => {
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<ButtonGroup variant='column'>
|
|
76
|
+
<Button variant='filled'>Primary</Button>
|
|
77
|
+
</ButtonGroup>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const ColumnTwo: StoryFn = () => {
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<ButtonGroup variant='column'>
|
|
85
|
+
<Button variant='outline'>Secondary</Button>
|
|
86
|
+
<Button variant='filled'>Primary</Button>
|
|
87
|
+
</ButtonGroup>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const ColumnThree: StoryFn = () => {
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<ButtonGroup variant='column'>
|
|
95
|
+
<Button variant='outline'>Secondary</Button>
|
|
96
|
+
<Button variant='filled'>Primary</Button>
|
|
97
|
+
<Button variant='transparent'>Tertiary</Button>
|
|
98
|
+
</ButtonGroup>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
export const CustomThree: StoryFn = () => {
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div className="">
|
|
107
|
+
<ButtonGroup variant='custom'
|
|
108
|
+
className='flex flex-row gap-4'
|
|
109
|
+
classNameButtons='size-14 border-4 bg-blue-200 border-purple-500'
|
|
110
|
+
>
|
|
111
|
+
|
|
112
|
+
<Button className='border-red-500 border-4 bg-white hover:bg-slate-200 hover:text-black'>A</Button>
|
|
113
|
+
<Button>B</Button>
|
|
114
|
+
<Button>C</Button>
|
|
115
|
+
</ButtonGroup>
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
// // Define "Alternate Classes" story
|
|
123
|
+
// export const AlternateClasses = {
|
|
124
|
+
// args: {
|
|
125
|
+
// children: 'Custom Classes',
|
|
126
|
+
// onClick: () => console.log('Clicked!'),
|
|
127
|
+
// className: 'border-8 border-black text-white bg-orange-500',
|
|
128
|
+
// }
|
|
129
|
+
// };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
2
|
+
import { vi } from 'vitest';
|
|
3
|
+
import { axe } from "vitest-axe";
|
|
4
|
+
import { ButtonGroup, ButtonGroupProps } from './ButtonGroup';
|
|
5
|
+
import { Button, ButtonProps } from './Button';
|
|
6
|
+
import { cp } from 'fs';
|
|
7
|
+
import { createRef } from 'react';
|
|
8
|
+
|
|
9
|
+
// getByText is synchronous
|
|
10
|
+
// findByText is asynchronous
|
|
11
|
+
|
|
12
|
+
describe('ButtonGroup Component', () => {
|
|
13
|
+
// Helper function to render the component with children
|
|
14
|
+
const renderComponent = (props: Partial<ButtonGroupProps> = {}) =>
|
|
15
|
+
render(
|
|
16
|
+
<ButtonGroup {...props}>
|
|
17
|
+
<Button>A</Button>
|
|
18
|
+
<Button>B</Button>
|
|
19
|
+
<Button>C</Button>
|
|
20
|
+
</ButtonGroup>
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
test('renders all children buttons', () => {
|
|
24
|
+
renderComponent();
|
|
25
|
+
expect(screen.getByText('A')).toBeInTheDocument();
|
|
26
|
+
expect(screen.getByText('B')).toBeInTheDocument();
|
|
27
|
+
expect(screen.getByText('C')).toBeInTheDocument();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
test('applies custom className to the container', () => {
|
|
32
|
+
const customContainerClass = 'custom-container';
|
|
33
|
+
const { container } = render(
|
|
34
|
+
<ButtonGroup className={customContainerClass}>
|
|
35
|
+
<Button>A</Button>
|
|
36
|
+
</ButtonGroup>
|
|
37
|
+
);
|
|
38
|
+
expect(container.firstChild).toHaveClass(customContainerClass);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('merges classNameButtons with each Button child', () => {
|
|
42
|
+
const groupButtonClass = 'group-spacing';
|
|
43
|
+
const customButtonClass = 'btn-custom';
|
|
44
|
+
const { container } = render(
|
|
45
|
+
<ButtonGroup classNameButtons={groupButtonClass}>
|
|
46
|
+
<Button classNameGroup={customButtonClass}>A</Button>
|
|
47
|
+
<Button>B</Button>
|
|
48
|
+
</ButtonGroup>
|
|
49
|
+
);
|
|
50
|
+
// Query all button elements (assumes Button renders a <button>)
|
|
51
|
+
const buttons = container.querySelectorAll('button');
|
|
52
|
+
expect(buttons.length).toBe(2);
|
|
53
|
+
|
|
54
|
+
// For the first button, verify that both the custom button class and group class are merged
|
|
55
|
+
expect(buttons[0].className).toContain(customButtonClass);
|
|
56
|
+
expect(buttons[0].className).toContain(groupButtonClass);
|
|
57
|
+
// For the second button, the group class should be present
|
|
58
|
+
expect(buttons[1].className).toContain(groupButtonClass);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('has no accessibility violations', async () => {
|
|
62
|
+
const { container } = renderComponent();
|
|
63
|
+
const results = await axe(container);
|
|
64
|
+
expect(results).toHaveNoViolations();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('forwards ref to the root element', () => {
|
|
68
|
+
const ref = createRef<HTMLDivElement>();
|
|
69
|
+
const { container } = render(
|
|
70
|
+
<ButtonGroup ref={ref}>
|
|
71
|
+
<Button>A</Button>
|
|
72
|
+
</ButtonGroup>
|
|
73
|
+
);
|
|
74
|
+
// The ref should be attached to the container (i.e. the first DOM element rendered)
|
|
75
|
+
expect(ref.current).toBe(container.firstChild);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('renders non-element children without modification', () => {
|
|
79
|
+
const plainText = 'Plain Text';
|
|
80
|
+
const { container } = render(
|
|
81
|
+
<ButtonGroup>
|
|
82
|
+
{plainText}
|
|
83
|
+
<Button>A</Button>
|
|
84
|
+
</ButtonGroup>
|
|
85
|
+
);
|
|
86
|
+
expect(container.textContent).toContain(plainText);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
|
|
2
|
+
/*
|
|
3
|
+
* Default is horizontal ('default'), optional is 'column'.
|
|
4
|
+
*
|
|
5
|
+
* Possible to have 1, 2, or 3 buttons in the predefined layout
|
|
6
|
+
*
|
|
7
|
+
* If the user chooses a 'custom' variant, we will forego the hard formatting on
|
|
8
|
+
* the buttons, i.e. 1st is secondary, 2nd is primary, etc.
|
|
9
|
+
*
|
|
10
|
+
* Scenarios for each of the two initial layouts, single, two, and three button
|
|
11
|
+
* versions of those layouts.
|
|
12
|
+
*
|
|
13
|
+
* In the three-button layout, the 'Tertiary' transparent
|
|
14
|
+
* button always appears below the first two, regardless of layout variant.
|
|
15
|
+
*
|
|
16
|
+
* I think we inform the developer, and give them example usage, that the primary
|
|
17
|
+
* button is filled, the secondary is outline, and the tertiary is transparent.
|
|
18
|
+
*
|
|
19
|
+
* Default layout:
|
|
20
|
+
* flex-row (1st two buttons)
|
|
21
|
+
* flex-row (3rd button)
|
|
22
|
+
*
|
|
23
|
+
* Columm Layout:
|
|
24
|
+
* flex row ( flex column --> two buttons)
|
|
25
|
+
* flex row (3rd button)
|
|
26
|
+
*
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { ButtonHTMLAttributes, Children, cloneElement, forwardRef, isValidElement, ReactElement, ReactNode, useEffect, useState } from "react";
|
|
30
|
+
import { twMerge } from 'tailwind-merge';
|
|
31
|
+
import { ButtonProps } from './Button';
|
|
32
|
+
|
|
33
|
+
interface VariantType {
|
|
34
|
+
[key: string]: {container: string, button: string,};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// const baseClasses = ' ';
|
|
38
|
+
|
|
39
|
+
const variants: VariantType = {
|
|
40
|
+
default: {
|
|
41
|
+
container: 'flex gap-4 w-full md:gap-8 lg:gap-12',
|
|
42
|
+
button: 'w-[150px]',
|
|
43
|
+
} ,
|
|
44
|
+
column: {
|
|
45
|
+
container: 'flex flex-col gap-3 w-full md:gap-4 lg:gap-6',
|
|
46
|
+
button: 'w-[150px]',
|
|
47
|
+
},
|
|
48
|
+
custom: {
|
|
49
|
+
container: '',
|
|
50
|
+
button: '',
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export interface ButtonGroupProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
55
|
+
children?: ReactNode; // children (buttons) that comprise the ButtonGroup
|
|
56
|
+
className?: string; // classname to alter div that contains the ButtonGroup
|
|
57
|
+
classNameButtons?: string; // classnames to alter boundary / spacing between buttons
|
|
58
|
+
variant?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const ButtonGroup = forwardRef<HTMLDivElement, ButtonGroupProps>(
|
|
62
|
+
({ className, children, classNameButtons, variant = 'default', ...props }, ref) => {
|
|
63
|
+
let size = 0;
|
|
64
|
+
|
|
65
|
+
const buttons = Children.map(children, (child) => {
|
|
66
|
+
if (isValidElement(child)) {
|
|
67
|
+
++size;
|
|
68
|
+
// Merge the existing className on the child with the classNameEdges prop
|
|
69
|
+
const element = child as ReactElement<ButtonProps>;
|
|
70
|
+
const childClassName = element.props.classNameGroup || "";
|
|
71
|
+
return cloneElement(element, {
|
|
72
|
+
classNameGroup: twMerge(childClassName, variants[variant].button, classNameButtons),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return child;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const tertiary:boolean = (size === 3) && (variant === 'default') ? true : false;
|
|
79
|
+
const one = buttons?.slice(0,2);
|
|
80
|
+
const two = buttons?.slice(2);
|
|
81
|
+
|
|
82
|
+
// three buttons & !custom, return tertiary on bottom row
|
|
83
|
+
if (tertiary)
|
|
84
|
+
return (
|
|
85
|
+
<div className='inline-flex flex-col gap-1'>
|
|
86
|
+
<div className={twMerge(variants[variant].container, className)}>
|
|
87
|
+
{one}
|
|
88
|
+
</div>
|
|
89
|
+
<div className={twMerge(variants[variant].container, 'justify-center', className)}>
|
|
90
|
+
{two}
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
else
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<div className={twMerge("", variants[variant].container, className)} ref={ref}>
|
|
99
|
+
{buttons}
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Chris suggested for debug component name labeling
|
|
107
|
+
ButtonGroup.displayName = 'SDK ButtonGroup'
|