@seeqdev/qomponents 0.0.183 → 0.0.185
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/README.md +5 -19
- package/dist/example/.eslintrc.cjs +2 -6
- package/dist/example/README.md +0 -3
- package/dist/example/src/Example.tsx +7 -7
- package/dist/example/tsconfig.json +2 -8
- package/dist/example/tsconfig.node.json +1 -3
- package/dist/example/vite.config.ts +1 -1
- package/dist/index.esm.js +784 -523
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +781 -520
- package/dist/index.js.map +1 -1
- package/dist/src/Accordion/Accordion.js +8 -0
- package/dist/src/Accordion/Accordion.stories.js +96 -0
- package/dist/src/Accordion/Accordion.test.js +54 -0
- package/dist/src/Accordion/Accordion.types.js +1 -0
- package/dist/src/Accordion/index.js +1 -0
- package/dist/src/Alert/Alert.js +33 -0
- package/dist/src/Alert/Alert.stories.d.ts +13 -5
- package/dist/src/Alert/Alert.stories.js +72 -0
- package/dist/src/Alert/Alert.test.js +49 -0
- package/dist/src/Alert/Alert.types.js +1 -0
- package/dist/src/Alert/index.js +1 -0
- package/dist/src/Button/Button.js +107 -0
- package/dist/src/Button/Button.stories.js +74 -0
- package/dist/src/Button/Button.test.js +65 -0
- package/dist/src/Button/Button.types.d.ts +4 -4
- package/dist/src/Button/Button.types.js +4 -0
- package/dist/src/Button/index.d.ts +1 -1
- package/dist/src/Button/index.js +1 -0
- package/dist/src/ButtonGroup/ButtonGroup.js +34 -0
- package/dist/src/ButtonGroup/ButtonGroup.stories.js +328 -0
- package/dist/src/ButtonGroup/ButtonGroup.test.js +65 -0
- package/dist/src/ButtonGroup/ButtonGroup.types.js +1 -0
- package/dist/src/ButtonGroup/index.js +1 -0
- package/dist/src/ButtonWithDropdown/ButtonWithDropdown.js +46 -0
- package/dist/src/ButtonWithDropdown/ButtonWithDropdown.stories.js +119 -0
- package/dist/src/ButtonWithDropdown/ButtonWithDropdown.test.js +92 -0
- package/dist/src/ButtonWithDropdown/ButtonWithDropdown.types.js +1 -0
- package/dist/src/ButtonWithDropdown/index.js +1 -0
- package/dist/src/ButtonWithPopover/ButtonWithPopover.js +54 -0
- package/dist/src/ButtonWithPopover/ButtonWithPopover.stories.js +51 -0
- package/dist/src/ButtonWithPopover/ButtonWithPopover.test.js +80 -0
- package/dist/src/ButtonWithPopover/ButtonWithPopover.types.js +1 -0
- package/dist/src/ButtonWithPopover/index.js +1 -0
- package/dist/src/Carousel/Carousel.js +80 -0
- package/dist/src/Carousel/Carousel.stories.js +100 -0
- package/dist/src/Carousel/Carousel.test.js +47 -0
- package/dist/src/Carousel/Carousel.types.js +1 -0
- package/dist/src/Carousel/index.js +1 -0
- package/dist/src/Checkbox/Checkbox.js +24 -0
- package/dist/src/Checkbox/Checkbox.stories.js +21 -0
- package/dist/src/Checkbox/Checkbox.test.js +93 -0
- package/dist/src/Checkbox/Checkbox.types.js +1 -0
- package/dist/src/Checkbox/index.js +1 -0
- package/dist/src/Collapse/Collapse.js +16 -0
- package/dist/src/Collapse/Collapse.stories.js +28 -0
- package/dist/src/Collapse/Collapse.test.js +16 -0
- package/dist/src/Collapse/Collapse.types.js +1 -0
- package/dist/src/Collapse/index.js +1 -0
- package/dist/src/Icon/Icon.js +55 -0
- package/dist/src/Icon/Icon.stories.js +31 -0
- package/dist/src/Icon/Icon.test.js +54 -0
- package/dist/src/Icon/Icon.types.d.ts +1 -1
- package/dist/src/Icon/Icon.types.js +15 -0
- package/dist/src/Icon/index.js +1 -0
- package/dist/src/InputGroup/InputGroup.js +33 -0
- package/dist/src/InputGroup/InputGroup.stories.js +144 -0
- package/dist/src/InputGroup/InputGroup.test.js +41 -0
- package/dist/src/InputGroup/InputGroup.types.js +1 -0
- package/dist/src/InputGroup/index.js +1 -0
- package/dist/src/Modal/Modal.js +86 -0
- package/dist/src/Modal/Modal.stories.js +79 -0
- package/dist/src/Modal/Modal.test.js +107 -0
- package/dist/src/Modal/Modal.types.js +1 -0
- package/dist/src/Modal/index.js +1 -0
- package/dist/src/ProgressBar/ProgressBar.js +71 -0
- package/dist/src/ProgressBar/ProgressBar.stories.js +46 -0
- package/dist/src/ProgressBar/ProgressBar.test.js +42 -0
- package/dist/src/ProgressBar/ProgressBar.types.js +1 -0
- package/dist/src/ProgressBar/index.js +1 -0
- package/dist/src/SeeqActionDropdown/SeeqActionDropdown.js +41 -0
- package/dist/src/SeeqActionDropdown/SeeqActionDropdown.stories.js +73 -0
- package/dist/src/SeeqActionDropdown/SeeqActionDropdown.test.js +72 -0
- package/dist/src/SeeqActionDropdown/SeeqActionDropdown.types.d.ts +0 -5
- package/dist/src/SeeqActionDropdown/SeeqActionDropdown.types.js +1 -0
- package/dist/src/SeeqActionDropdown/index.js +1 -0
- package/dist/src/SeeqActionDropdown/variants.js +34 -0
- package/dist/src/Select/Select.js +171 -0
- package/dist/src/Select/Select.stories.js +50 -0
- package/dist/src/Select/Select.test.js +176 -0
- package/dist/src/Select/Select.types.js +1 -0
- package/dist/src/Select/index.js +2 -0
- package/dist/src/Slider/Slider.js +11 -0
- package/dist/src/Slider/Slider.stories.js +37 -0
- package/dist/src/Slider/Slider.test.js +31 -0
- package/dist/src/Slider/Slider.types.js +1 -0
- package/dist/src/Slider/index.js +1 -0
- package/dist/src/SvgIcon/SvgIcon.js +27 -0
- package/dist/src/SvgIcon/SvgIcon.stories.js +26 -0
- package/dist/src/SvgIcon/SvgIcon.test.js +40 -0
- package/dist/src/SvgIcon/SvgIcon.types.d.ts +1 -1
- package/dist/src/SvgIcon/SvgIcon.types.js +2 -0
- package/dist/src/SvgIcon/index.js +1 -0
- package/dist/src/Tabs/Tabs.js +18 -0
- package/dist/src/Tabs/Tabs.stories.js +83 -0
- package/dist/src/Tabs/Tabs.test.js +91 -0
- package/dist/src/Tabs/Tabs.types.js +1 -0
- package/dist/src/Tabs/index.js +1 -0
- package/dist/src/TextArea/TextArea.js +54 -0
- package/dist/src/TextArea/TextArea.stories.js +21 -0
- package/dist/src/TextArea/TextArea.test.js +129 -0
- package/dist/src/TextArea/TextArea.types.js +1 -0
- package/dist/src/TextArea/index.js +1 -0
- package/dist/src/TextField/TextField.js +85 -0
- package/dist/src/TextField/TextField.stories.js +30 -0
- package/dist/src/TextField/TextField.test.js +40 -0
- package/dist/src/TextField/TextField.types.js +1 -0
- package/dist/src/TextField/index.js +1 -0
- package/dist/src/ToolbarButton/ToolbarButton.js +58 -0
- package/dist/src/ToolbarButton/ToolbarButton.stories.js +63 -0
- package/dist/src/ToolbarButton/ToolbarButton.test.js +89 -0
- package/dist/src/ToolbarButton/ToolbarButton.types.d.ts +1 -1
- package/dist/src/ToolbarButton/ToolbarButton.types.js +1 -0
- package/dist/src/ToolbarButton/index.js +1 -0
- package/dist/src/Tooltip/QTip.stories.js +22 -0
- package/dist/src/Tooltip/QTip.types.js +1 -0
- package/dist/src/Tooltip/QTipPerformance.stories.js +27 -0
- package/dist/src/Tooltip/Qtip.js +168 -0
- package/dist/src/Tooltip/Tooltip.js +34 -0
- package/dist/src/Tooltip/Tooltip.stories.js +20 -0
- package/dist/src/Tooltip/Tooltip.types.d.ts +1 -1
- package/dist/src/Tooltip/Tooltip.types.js +2 -0
- package/dist/src/Tooltip/TooltipPerformance.stories.js +25 -0
- package/dist/src/Tooltip/index.js +2 -0
- package/dist/src/Tooltip/qTip.utilities.js +10 -0
- package/dist/src/index.js +47 -0
- package/dist/src/setupTests.js +5 -0
- package/dist/src/types.js +25 -0
- package/dist/src/utils/browserId.d.ts +3 -3
- package/dist/src/utils/browserId.js +28 -0
- package/dist/src/utils/svg.js +19 -0
- package/dist/src/utils/validateStyleDimension.js +13 -0
- package/dist/src/utils/validateStyleDimension.test.js +19 -0
- package/dist/styles.css +3410 -2791
- package/package.json +22 -17
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import '../styles.css';
|
|
3
|
+
import { getQTipData } from '../Tooltip/qTip.utilities';
|
|
4
|
+
import { getSvgIconPath } from '../utils/svg';
|
|
5
|
+
/**
|
|
6
|
+
* Renders an icon that has an SVG icon path (see {@link isSvgIcon() })
|
|
7
|
+
*
|
|
8
|
+
* @param onClick - function to call when clicking the icon (takes no parameters)
|
|
9
|
+
* @param id - id that can be placed on the SvgIcon component
|
|
10
|
+
* @param testId - id that will be used in the data-testid attribute on the icon
|
|
11
|
+
* @param customId - id that will be used in the data-customid attribute on the icon. Can be used to identify the icon
|
|
12
|
+
* as the click event target in an event handler
|
|
13
|
+
* @param icon - the SVG icon
|
|
14
|
+
* @param color - used to add a custom color to the icon (required if type="color")
|
|
15
|
+
* @param extraClassNames - extra class names to apply
|
|
16
|
+
* @param [viewBox='0 0 19 19'] - optional SVG view box
|
|
17
|
+
* @param type - default will use dark/light text colors otherwise will use the theme color
|
|
18
|
+
* @param tooltipProps - props to pass to the tooltip
|
|
19
|
+
*/
|
|
20
|
+
const SvgIcon = ({ onClick, icon, color, type = 'default', extraClassNames, viewBox = '0 0 19 19', id, testId, customId, ...tooltipProps }) => {
|
|
21
|
+
const appliedClassNames = `${onClick ? 'tw-cursor-pointer' : ''} ${extraClassNames} focus:tw-outline-none focus-visible:tw-outline-none
|
|
22
|
+
tw-outline-none`;
|
|
23
|
+
const tooltipData = getQTipData(tooltipProps);
|
|
24
|
+
const appliedType = type === 'default' ? 'currentColor' : 'rgb(var(--sq-icon))';
|
|
25
|
+
return (_jsx("div", { onClick: onClick, ...tooltipData, className: `svgContainer ${appliedClassNames}`, children: _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: `tw-sq-icon dark:tw-text-white`, viewBox: viewBox, fill: color ? color : appliedType, "data-testid": testId, children: _jsx("path", { d: getSvgIconPath(icon) }) }) }));
|
|
26
|
+
};
|
|
27
|
+
export default SvgIcon;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import SvgIcon from './SvgIcon';
|
|
3
|
+
import { svgIconTypes } from './SvgIcon.types';
|
|
4
|
+
export default {
|
|
5
|
+
title: 'Svg Icons',
|
|
6
|
+
};
|
|
7
|
+
const icon = 'svgpath:M0 8.8153a8.8153 8.8153 90 1117.6306 0A8.8153 8.8153 90 110 8.8153zM9.9172 3.3057a1.1019' +
|
|
8
|
+
' 1.1019 90 10-2.2038 0 1.1019 1.1019 90 102.2038 0zM8.8153 14.3249c1.2155 0 2.2038-.9883 2.2038-2.2038 0-.5992-.2376-1.1398-.6233-1.5358L12.6031 5.5681c.1825-.4167-.0069-.9056-.4235-1.0881s-.9056.0069-1.0881.4235L8.8807 9.9172c-.0207 0-.0448 0-.0654 0-1.2155 0-2.2038.9883-2.2038 2.2038s.9883 2.2038 2.2038 2.2038zM6.0605 4.9586a1.1019 1.1019 90 10-2.2038 0 1.1019 1.1019 90 102.2038 0zM3.3057 9.9172a1.1019 1.1019 90 100-2.2038 1.1019 1.1019 90 100 2.2038zm12.121-1.1019a1.1019 1.1019 90 10-2.2038 0 1.1019 1.1019 90 102.2038 0z';
|
|
9
|
+
export const AllIcons = () => {
|
|
10
|
+
const renderAllVariations = () => {
|
|
11
|
+
const supportedSvgIconTypes = svgIconTypes.filter((type) => type === 'default' || type === 'theme');
|
|
12
|
+
return (_jsxs(_Fragment, { children: [
|
|
13
|
+
_jsx("br", {}), _jsx("br", {}), supportedSvgIconTypes.map((iconType) => {
|
|
14
|
+
return (_jsxs("div", { children: [
|
|
15
|
+
_jsxs("b", { children: ["type=", iconType] }), _jsx("br", {}), _jsx("div", { className: "tw-flex", children: _jsx(SvgIcon, { icon: icon, type: iconType, extraClassNames: 'tw-p-2 tw-w-12', onClick: (e) => window.alert('svg clicked') }) }), _jsx("div", { className: "tw-dark tw-bg-sq-dark-background tw-flex", children: _jsx(SvgIcon, { icon: icon, type: iconType, extraClassNames: 'tw-p-2 tw-w-12', onClick: (e) => window.alert('svg clicked') }) }), _jsx("br", {})
|
|
16
|
+
] }, `${iconType}`));
|
|
17
|
+
})] }));
|
|
18
|
+
};
|
|
19
|
+
return (_jsxs("div", { className: "tw-grid tw-grid-cols-4 tw-gap-4", children: [
|
|
20
|
+
_jsxs("div", { className: "color_topic", children: [
|
|
21
|
+
_jsx("b", { children: "Topic Colors" }), renderAllVariations()] }), _jsxs("div", { className: "color_analysis", children: [
|
|
22
|
+
_jsx("b", { children: "Analysis Colors" }), renderAllVariations()] }), _jsxs("div", { className: "color_datalab", children: [
|
|
23
|
+
_jsx("b", { children: "Datalab Colors" }), renderAllVariations()] }), _jsxs("div", { className: "color_vantage", children: [
|
|
24
|
+
_jsx("b", { children: "Vantage Colors" }), renderAllVariations()] })
|
|
25
|
+
] }));
|
|
26
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import '@testing-library/jest-dom';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import SvgIcon from './SvgIcon';
|
|
5
|
+
describe('SvgIcon', () => {
|
|
6
|
+
class Context {
|
|
7
|
+
testId = 'svgIconTestId';
|
|
8
|
+
icon = 'svgpath:M0 8.8153a8.8153 8.8153 90 1117.6306 0A8.8153 8.8153 90 110 8.8153zM9.9172 3.3057a1.1019 1.1019' +
|
|
9
|
+
' 90 10-2.2038 0 1.1019 1.1019 90 102.2038 0zM8.8153 14.3249c1.2155 0 2.2038-.9883 2.2038-2.2038' +
|
|
10
|
+
' 0-.5992-.2376-1.1398-.6233-1.5358L12.6031 5.5681c.1825-.4167-.0069-.9056-.4235-1.0881s-.9056.0069-1.0881.4235L8.8807 9.9172c-.0207 0-.0448 0-.0654 0-1.2155 0-2.2038.9883-2.2038 2.2038s.9883 2.2038 2.2038 2.2038zM6.0605 4.9586a1.1019 1.1019 90 10-2.2038 0 1.1019 1.1019 90 102.2038 0zM3.3057 9.9172a1.1019 1.1019 90 100-2.2038 1.1019 1.1019 90 100 2.2038zm12.121-1.1019a1.1019 1.1019 90 10-2.2038 0 1.1019 1.1019 90 102.2038 0z';
|
|
11
|
+
props = {
|
|
12
|
+
icon: this.icon,
|
|
13
|
+
onClick: jest.fn(),
|
|
14
|
+
testId: this.testId,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
let tc;
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
tc = new Context();
|
|
20
|
+
});
|
|
21
|
+
const renderIcon = (props) => render(_jsx(SvgIcon, { ...props }));
|
|
22
|
+
it('renders svg icon', () => {
|
|
23
|
+
renderIcon(tc.props);
|
|
24
|
+
expect(screen.getByTestId(tc.testId)).toHaveAttribute('fill');
|
|
25
|
+
});
|
|
26
|
+
describe('icon types', () => {
|
|
27
|
+
it('renders the theme type by default', () => {
|
|
28
|
+
renderIcon(tc.props);
|
|
29
|
+
expect(screen.getByTestId(tc.testId)).toHaveClass('tw-sq-icon');
|
|
30
|
+
});
|
|
31
|
+
it('renders the white icon', () => {
|
|
32
|
+
renderIcon({ ...tc.props, color: 'white' });
|
|
33
|
+
expect(screen.getByTestId(tc.testId)).toHaveAttribute('fill', 'white');
|
|
34
|
+
});
|
|
35
|
+
it('renders the color icon', () => {
|
|
36
|
+
renderIcon({ ...tc.props, color: '#AABBFF' });
|
|
37
|
+
expect(screen.getByTestId(tc.testId)).toHaveAttribute('fill', '#AABBFF');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TooltipComponentProps } from '../Tooltip/Tooltip.types';
|
|
2
2
|
export declare const svgIconTypes: readonly ["theme", "white", "dark-gray", "darkish-gray", "gray", "color", "info", "text", "warning", "inherit", "danger", "theme-light", "success", "default"];
|
|
3
|
-
export type SvgIconType =
|
|
3
|
+
export type SvgIconType = typeof svgIconTypes[number];
|
|
4
4
|
/**
|
|
5
5
|
* Props for the SvgIcon component that renders SVG-based icons with various styling options.
|
|
6
6
|
* Extends TooltipComponentProps to support tooltip functionality on the icon.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './SvgIcon';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Content, List, Root, Trigger } from '@radix-ui/react-tabs';
|
|
3
|
+
import Icon from '../Icon';
|
|
4
|
+
const Tabs = ({ tabs, defaultActiveTab, activeTab, onTabSelect, extraClassNames, testId, id, stretchTabs = false, }) => {
|
|
5
|
+
const handleTabSelect = (tabId) => {
|
|
6
|
+
if (activeTab === tabId)
|
|
7
|
+
return;
|
|
8
|
+
onTabSelect && onTabSelect(tabId);
|
|
9
|
+
};
|
|
10
|
+
return (_jsxs(Root, { className: `tw-flex tw-flex-col tw-min-w-[300px] tw-max-w-full tw-flex-grow focus-visible:tw-outline-none ${extraClassNames || ''}`, defaultValue: defaultActiveTab, "data-testid": testId, id: id, value: activeTab, onValueChange: handleTabSelect, children: [
|
|
11
|
+
_jsx(List, { className: `tw-flex tw-flex-row tw-flex-wrap tw-z-[1001]`, children: tabs.map(({ id, icon, label, tabExtraClassNames, testId: tabsTestId, disabled, tabLabelExtraClassNames = '' }, index) => (_jsx(Trigger, { className: `tw-bg-sq-white dark:tw-bg-sq-dark-background tw-h-[25px] focus-visible:tw-outline-none tw-min-w-[100px] tw-px-4 tw-border-solid dark:tw-border-gray-700 tw-flex tw-flex-1 tw-justify-center tw-items-center tw-border-r-[2px] last:tw-border-r-0 ${stretchTabs ? '' : 'tw-max-w-max tw-min-w-fit'} ${tabExtraClassNames || ''} ${activeTab === id
|
|
12
|
+
? 'tw-border-b-sq-color-dark dark:tw-border-b-sq-color-dark tw-border-b-[3px]'
|
|
13
|
+
: 'hover:tw-bg-sq-light-gray tw-border-b-[1px] hover:dark:tw-bg-gray-700'}`, "data-testid": tabsTestId, disabled: disabled, value: id, children: _jsxs("span", { className: "tw-flex tw-items-center tw-overflow-hidden tw-pb-[3px]", children: [icon && (_jsx(Icon, { icon: icon, testId: `${id}_tab-icon`, extraClassNames: "tw-text-[0.9375rem] tw-mr-[7px] tw-flex-shrink-0" })), _jsx("span", { className: `tw-text-[0.875rem] tw-font-medium tw-whitespace-nowrap tw-overflow-hidden tw-text-ellipsis ${activeTab === id
|
|
14
|
+
? 'dark:tw-text-sq-dark-text tw-text-gray-500'
|
|
15
|
+
: 'dark:tw-text-sq-color-dark-dark tw-text-sq-color-dark'} ${tabLabelExtraClassNames}`, children: label })
|
|
16
|
+
] }) }, `${label}-${id}-${index}`))) }), tabs.map((tab, index) => (_jsx(Content, { className: `tw-bg-sq-white dark:tw-bg-sq-dark-background tw-overflow-y-auto -tw-mt-[1px] tw-border-t-[1px] dark:tw-border-gray-700 tw-z-[500] tw-flex tw-flex-col tw-flex-grow focus-visible:tw-outline-none ${tab.tabContentExtraClassNames || ''}`, value: tab.id, children: tab.content }, `${tab.label}_${index}_content`)))] }));
|
|
17
|
+
};
|
|
18
|
+
export default Tabs;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import Tabs from './Tabs';
|
|
4
|
+
import Button from '../Button';
|
|
5
|
+
export default {
|
|
6
|
+
title: 'Tabs',
|
|
7
|
+
};
|
|
8
|
+
const renderData = () => (_jsxs("div", { className: "tw-text-sq-color-gray dark:tw-text-sq-white", children: [
|
|
9
|
+
_jsx("p", { className: "tw-mb-5 tw-text-[0.9375rem] tw-leading-normal", children: "This is a data tab to show details about your data. You can make changes to your account" }), _jsx(Button, { variant: "outline", label: "Save data" })
|
|
10
|
+
] }));
|
|
11
|
+
const renderTools = () => (_jsxs("div", { className: "tw-text-sq-color-gray dark:tw-text-sq-white", children: [
|
|
12
|
+
_jsx("p", { className: "tw-mb-5 tw-text-[0.9375rem] tw-leading-normal", children: "Make changes to your account here. Click save when you're done." }), _jsx(Button, { variant: "outline", label: "Save tools" })
|
|
13
|
+
] }));
|
|
14
|
+
const renderJournal = () => (_jsxs("div", { className: "tw-text-sq-color-gray dark:tw-text-sq-white", children: [
|
|
15
|
+
_jsx("p", { className: "tw-mb-5 tw-text-[0.9375rem] tw-leading-normal", children: "Make changes to your account here. Click save when you're done." }), _jsx(Button, { variant: "outline", label: "Save journal" })
|
|
16
|
+
] }));
|
|
17
|
+
const tabsList = [
|
|
18
|
+
{
|
|
19
|
+
id: 'data',
|
|
20
|
+
label: 'Data',
|
|
21
|
+
icon: 'fc-data',
|
|
22
|
+
content: renderData(),
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'tools',
|
|
26
|
+
label: 'Tools',
|
|
27
|
+
icon: 'fc-gears-2',
|
|
28
|
+
content: renderTools(),
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'journal',
|
|
32
|
+
label: 'Journal',
|
|
33
|
+
icon: 'fc-workbook-lock',
|
|
34
|
+
content: renderJournal(),
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
const extraTabsList = [
|
|
38
|
+
{
|
|
39
|
+
id: 'data',
|
|
40
|
+
label: 'Data',
|
|
41
|
+
icon: 'fc-data',
|
|
42
|
+
content: renderData(),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'tools',
|
|
46
|
+
label: 'Tools',
|
|
47
|
+
icon: 'fc-gears-2',
|
|
48
|
+
content: renderTools(),
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'journal',
|
|
52
|
+
label: 'Journal',
|
|
53
|
+
icon: 'fc-workbook-lock',
|
|
54
|
+
content: renderJournal(),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'journal2',
|
|
58
|
+
label: 'Journal',
|
|
59
|
+
icon: 'fc-workbook-lock',
|
|
60
|
+
disabled: true,
|
|
61
|
+
content: renderJournal(),
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'data3',
|
|
65
|
+
label: 'Data 2',
|
|
66
|
+
icon: 'fc-data',
|
|
67
|
+
disabled: true,
|
|
68
|
+
content: renderData(),
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
const colors = ['topic', 'analysis', 'datalab', 'vantage'];
|
|
72
|
+
export const AllTabsVariants = () => {
|
|
73
|
+
const [activeTab, setActiveTab] = React.useState('data');
|
|
74
|
+
const renderAllVariations = (color) => (_jsxs("div", { children: [
|
|
75
|
+
_jsx("div", { className: "tw-p-4", children: _jsx(Tabs, { stretchTabs: true, activeTab: activeTab, onTabSelect: setActiveTab, defaultActiveTab: "data", tabs: tabsList }) }), _jsx("div", { className: "tw-p-4", children: _jsx(Tabs, { activeTab: activeTab, onTabSelect: (tabId) => setActiveTab(tabId), defaultActiveTab: "tool", tabs: extraTabsList.map((tab) => ({ ...tab, icon: undefined, tabExtraClassNames: 'tw-max-w-max' })) }) }), _jsxs("div", { className: "tw-p-4 tw-dark tw-bg-sq-dark-background ", children: [
|
|
76
|
+
_jsxs("p", { className: "tw-text-sq-color-dark dark:tw-text-sq-color-dark-dark tw-mb-8", children: [color[0].toUpperCase() + color.slice(1), " Dark mode"] }), _jsx(Tabs, { activeTab: activeTab, onTabSelect: (tabId) => setActiveTab(tabId), defaultActiveTab: "tools", tabs: tabsList.map((tab) => ({ ...tab, icon: undefined })) })
|
|
77
|
+
] })
|
|
78
|
+
] }, color + '_wrapper'));
|
|
79
|
+
return (_jsx("div", { className: "tw-grid tw-grid-cols-4 tw-gap-4", children: colors.map((color) => {
|
|
80
|
+
return (_jsxs("div", { className: `color_${color}`, children: [
|
|
81
|
+
_jsxs("b", { children: [color[0].toUpperCase() + color.slice(1), " Colors"] }), renderAllVariations(color)] }, color));
|
|
82
|
+
}) }));
|
|
83
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import { render, screen } from '@testing-library/react';
|
|
5
|
+
import userEvent from '@testing-library/user-event';
|
|
6
|
+
import Tabs from './Tabs';
|
|
7
|
+
const renderData = () => (_jsxs("div", { className: "tw-text-sq-color-gray dark:tw-text-sq-white", children: [
|
|
8
|
+
_jsx("p", { className: "tw-mb-5 tw-text-[0.9375rem] tw-leading-normal", children: "This is a data tab to show details about your data. You can make changes to your account" }), _jsx("button", { children: "save data" })
|
|
9
|
+
] }));
|
|
10
|
+
const renderTools = () => (_jsxs("div", { className: "tw-text-sq-color-gray dark:tw-text-sq-white", children: [
|
|
11
|
+
_jsx("p", { className: "tw-mb-5 tw-text-[0.9375rem] tw-leading-normal", children: "Make changes to your account here. Click save when you're done." }), _jsx("button", { children: "save tools" })
|
|
12
|
+
] }));
|
|
13
|
+
const renderJournal = () => (_jsxs("div", { className: "tw-text-sq-color-gray dark:tw-text-sq-white", children: [
|
|
14
|
+
_jsx("p", { className: "tw-mb-5 tw-text-[0.9375rem] tw-leading-normal", children: "Make changes to your account here. Click save when you're done." }), _jsx("button", { children: "save journal" })
|
|
15
|
+
] }));
|
|
16
|
+
const tabsList = [
|
|
17
|
+
{
|
|
18
|
+
id: 'data',
|
|
19
|
+
label: 'Data',
|
|
20
|
+
icon: 'fc-data',
|
|
21
|
+
content: renderData(),
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'tools',
|
|
25
|
+
label: 'Tools',
|
|
26
|
+
icon: 'fc-gears-2',
|
|
27
|
+
content: renderTools(),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'journal',
|
|
31
|
+
label: 'Journal',
|
|
32
|
+
icon: 'fc-workbook-lock',
|
|
33
|
+
content: renderJournal(),
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
const mockTabsClick = jest.fn();
|
|
37
|
+
const SampleComponent = (props) => {
|
|
38
|
+
const [activeTab, setActiveTab] = React.useState('data');
|
|
39
|
+
return (_jsx(Tabs, { ...props, activeTab: activeTab, onTabSelect: (tabId) => {
|
|
40
|
+
setActiveTab(tabId);
|
|
41
|
+
mockTabsClick(tabId);
|
|
42
|
+
} }));
|
|
43
|
+
};
|
|
44
|
+
describe('Tabs', () => {
|
|
45
|
+
class Context {
|
|
46
|
+
props = {
|
|
47
|
+
tabs: tabsList,
|
|
48
|
+
defaultActiveTab: 'data',
|
|
49
|
+
activeTab: 'data',
|
|
50
|
+
id: 'tabs-table',
|
|
51
|
+
onTabSelect: jest.fn(),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
let tc;
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
tc = new Context();
|
|
57
|
+
});
|
|
58
|
+
const renderTabs = (props) => render(_jsx(SampleComponent, { ...props }));
|
|
59
|
+
it('renders Tabs label with correct content based on default active tab', () => {
|
|
60
|
+
renderTabs(tc.props);
|
|
61
|
+
expect(screen.getByText('Data')).toBeInTheDocument();
|
|
62
|
+
expect(screen.getByText('Tools')).toBeInTheDocument();
|
|
63
|
+
expect(screen.getByText('Journal')).toBeInTheDocument();
|
|
64
|
+
expect(screen.getByTestId('journal_tab-icon')).toBeInTheDocument();
|
|
65
|
+
expect(screen.getByText('save data')).toBeInTheDocument();
|
|
66
|
+
expect(screen.queryByText('save tools')).not.toBeInTheDocument();
|
|
67
|
+
expect(screen.queryByText('save journals')).not.toBeInTheDocument();
|
|
68
|
+
});
|
|
69
|
+
it('renders correct content after tab is selected', async () => {
|
|
70
|
+
renderTabs(tc.props);
|
|
71
|
+
expect(screen.getByText('save data')).toBeInTheDocument();
|
|
72
|
+
await userEvent.click(screen.getByText('Tools'));
|
|
73
|
+
expect(mockTabsClick).toHaveBeenCalled();
|
|
74
|
+
expect(screen.getByText('save tools')).toBeInTheDocument();
|
|
75
|
+
});
|
|
76
|
+
it('does not render icon if it is not provided', async () => {
|
|
77
|
+
renderTabs({ ...tc.props, tabs: tc.props.tabs.map((tab) => ({ ...tab, icon: undefined })) });
|
|
78
|
+
expect(screen.queryByText('journal_tab-icon')).not.toBeInTheDocument();
|
|
79
|
+
});
|
|
80
|
+
it('calls the onTabSelect callback when a new tab is clicked', async () => {
|
|
81
|
+
mockTabsClick.mockClear();
|
|
82
|
+
renderTabs(tc.props);
|
|
83
|
+
await userEvent.click(screen.getByText('Journal'));
|
|
84
|
+
expect(mockTabsClick).toBeCalledWith('journal');
|
|
85
|
+
expect(mockTabsClick).toBeCalledTimes(1);
|
|
86
|
+
mockTabsClick.mockClear();
|
|
87
|
+
await userEvent.click(screen.getByText('Tools'));
|
|
88
|
+
expect(mockTabsClick).toBeCalledWith('tools');
|
|
89
|
+
expect(mockTabsClick).toBeCalledTimes(1);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './Tabs';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React, { useRef, useEffect } from 'react';
|
|
3
|
+
import '../styles.css';
|
|
4
|
+
const baseClasses = 'tw-leading-normal tw-outline-none tw-py-1 tw-px-3 tw-rounded-md tw-w-full' +
|
|
5
|
+
' disabled:tw-pointer-events-none disabled:tw-bg-sq-light-gray disabled:dark:tw-bg-sq-dark-disabled-gray' +
|
|
6
|
+
' disabled:tw-cursor-not-allowed tw-p-1 tw-border-solid tw-border tw-text-sm';
|
|
7
|
+
const darkTheme = 'dark:tw-bg-sq-dark-background dark:tw-text-sq-dark-text ' +
|
|
8
|
+
'dark:tw-placeholder-sq-dark-text-lighter disabled:dark:tw-text-sq-dark-text-lighter';
|
|
9
|
+
const lightTheme = 'tw-text-sq-text-color tw-placeholder-gray-400 disabled:tw-text-sq-darkish-gray';
|
|
10
|
+
const errorClasses = 'tw-border-sq-danger-color';
|
|
11
|
+
const borderColorClasses = [
|
|
12
|
+
'tw-border-sq-disabled-gray',
|
|
13
|
+
'dark:tw-border-sq-dark-disabled-gray',
|
|
14
|
+
'dark:focus:tw-border-sq-color-dark-dark',
|
|
15
|
+
'dark:active:tw-border-sq-color-dark-dark',
|
|
16
|
+
'focus:tw-border-sq-color-dark',
|
|
17
|
+
'active:tw-border-sq-color-dark',
|
|
18
|
+
].join(' ');
|
|
19
|
+
/**
|
|
20
|
+
* TextArea.
|
|
21
|
+
*/
|
|
22
|
+
export const TextArea = React.forwardRef(({ readonly = false, disabled = false, onChange, onKeyUp, onFocus, onBlur, onKeyDown, onSelectionChange, id, name, rows = 3, cols = undefined, value, placeholder, showError, extraClassNames, testId, autoFocus = false, }, ref) => {
|
|
23
|
+
const appliedClasses = `${baseClasses} ${extraClassNames} ${lightTheme} ${darkTheme} ${showError ? errorClasses : borderColorClasses}`;
|
|
24
|
+
const textareaRef = useRef(null);
|
|
25
|
+
// Handle selection change events
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!onSelectionChange || !textareaRef.current) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const textarea = textareaRef.current;
|
|
31
|
+
const handleSelectionChange = (e) => {
|
|
32
|
+
onSelectionChange(e);
|
|
33
|
+
};
|
|
34
|
+
// Add event listeners for selection changes
|
|
35
|
+
textarea.addEventListener('selectionchange', handleSelectionChange);
|
|
36
|
+
// Also listen for mouse and keyboard events that change selection
|
|
37
|
+
textarea.addEventListener('mouseup', handleSelectionChange);
|
|
38
|
+
textarea.addEventListener('keyup', handleSelectionChange);
|
|
39
|
+
return () => {
|
|
40
|
+
textarea.removeEventListener('selectionchange', handleSelectionChange);
|
|
41
|
+
textarea.removeEventListener('mouseup', handleSelectionChange);
|
|
42
|
+
textarea.removeEventListener('keyup', handleSelectionChange);
|
|
43
|
+
};
|
|
44
|
+
}, [onSelectionChange]);
|
|
45
|
+
return (_jsx("textarea", { ref: (element) => {
|
|
46
|
+
textareaRef.current = element;
|
|
47
|
+
if (typeof ref === 'function') {
|
|
48
|
+
ref(element);
|
|
49
|
+
}
|
|
50
|
+
else if (ref) {
|
|
51
|
+
ref.current = element;
|
|
52
|
+
}
|
|
53
|
+
}, "data-testid": testId, name: name, id: id, value: value, className: appliedClasses, placeholder: placeholder, disabled: disabled, readOnly: readonly, onChange: onChange, onFocus: onFocus, onBlur: onBlur, onKeyDown: onKeyDown, onKeyUp: onKeyUp, rows: rows, cols: cols, autoFocus: autoFocus }));
|
|
54
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { TextArea } from './TextArea';
|
|
3
|
+
export default {
|
|
4
|
+
title: 'TextArea',
|
|
5
|
+
};
|
|
6
|
+
export const AllTextAreas = () => {
|
|
7
|
+
const renderAllVariations = () => (_jsxs(_Fragment, { children: [
|
|
8
|
+
_jsxs("div", { className: "tw-p-4 light", children: [
|
|
9
|
+
_jsx("div", { className: "tw-p-4", children: _jsx(TextArea, { value: "Short text provided." }) }), _jsx("div", { className: "tw-p-4", children: _jsx(TextArea, { value: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Odio morbi quis commodo odio aenean. Lectus sit amet est placerat in egestas erat. Dui nunc mattis enim ut tellus. Dictumst vestibulum rhoncus est pellentesque elit ullamcorper. Nibh mauris cursus mattis molestie a. Aenean vel elit scelerisque mauris pellentesque. Nunc sed id semper risus in hendrerit gravida rutrum quisque. Tortor dignissim convallis aenean et tortor at risus. Vitae congue mauris rhoncus aenean vel elit scelerisque. Tellus id interdum velit laoreet id donec ultrices. Ac turpis egestas maecenas pharetra. Commodo odio aenean sed adipiscing diam donec adipiscing tristique risus. Consequat interdum varius sit amet mattis. Lacus laoreet non curabitur gravida arcu ac tortor. Vulputate sapien nec sagittis aliquam malesuada bibendum arcu vitae elementum. Odio ut enim blandit volutpat maecenas volutpat." }) }), _jsx("div", { className: "tw-p-4", children: _jsx(TextArea, { placeholder: "placeholder text" }) }), _jsx("div", { className: "tw-p-4", children: _jsx(TextArea, { placeholder: "with error", showError: true }) }), _jsx("div", { className: "tw-p-4", children: _jsx(TextArea, { value: "read-only", readonly: true, onFocus: (e) => e.currentTarget.select() }) }), _jsx("div", { className: "tw-p-4", children: _jsx(TextArea, { value: "disabled", disabled: true }) })
|
|
10
|
+
] }), _jsxs("div", { className: "tw-p-4 tw-dark tw-bg-sq-dark-background", children: [
|
|
11
|
+
_jsx("div", { className: "tw-p-4", children: _jsx(TextArea, { value: "value provided" }) }), _jsx("div", { className: "tw-p-4", children: _jsx(TextArea, { placeholder: "placeholder text" }) }), _jsx("div", { className: "tw-p-4", children: _jsx(TextArea, { placeholder: "with error", showError: true }) }), _jsx("div", { className: "tw-p-4", children: _jsx(TextArea, { value: "read-only", readonly: true, onFocus: (e) => e.currentTarget.select() }) }), _jsx("div", { className: "tw-p-4", children: _jsx(TextArea, { value: "disabled", disabled: true }) })
|
|
12
|
+
] })
|
|
13
|
+
] }));
|
|
14
|
+
return (_jsxs("div", { className: "tw-grid tw-grid-cols-4 tw-gap-4", children: [
|
|
15
|
+
_jsxs("div", { className: "color_topic", children: [
|
|
16
|
+
_jsx("b", { children: "Topic Colors" }), renderAllVariations()] }), _jsxs("div", { className: "color_analysis", children: [
|
|
17
|
+
_jsx("b", { children: "Analysis Colors" }), renderAllVariations()] }), _jsxs("div", { className: "color_datalab", children: [
|
|
18
|
+
_jsx("b", { children: "Datalab Colors" }), renderAllVariations()] }), _jsxs("div", { className: "color_vantage", children: [
|
|
19
|
+
_jsx("b", { children: "Vantage Colors" }), renderAllVariations()] })
|
|
20
|
+
] }));
|
|
21
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import { render, screen } from '@testing-library/react';
|
|
5
|
+
import userEvent from '@testing-library/user-event';
|
|
6
|
+
import { TextArea } from './TextArea';
|
|
7
|
+
describe('TextArea', () => {
|
|
8
|
+
class Context {
|
|
9
|
+
testId = 'textAreaTestId';
|
|
10
|
+
props = {
|
|
11
|
+
onChange: jest.fn(),
|
|
12
|
+
onKeyUp: jest.fn(),
|
|
13
|
+
testId: this.testId,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
let tc;
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
tc = new Context();
|
|
19
|
+
});
|
|
20
|
+
const renderTextArea = (props) => render(_jsx(TextArea, { ...props }));
|
|
21
|
+
it('renders the provided value', () => {
|
|
22
|
+
const value = 'hello, this is text for a text area.';
|
|
23
|
+
renderTextArea({ ...tc.props, value });
|
|
24
|
+
expect(screen.getByDisplayValue(value)).toBeInTheDocument();
|
|
25
|
+
});
|
|
26
|
+
it('renders the provided placeholder', () => {
|
|
27
|
+
const placeholder = 'Prompt to enter';
|
|
28
|
+
renderTextArea({ ...tc.props, placeholder });
|
|
29
|
+
expect(screen.getByPlaceholderText(placeholder)).toBeInTheDocument();
|
|
30
|
+
});
|
|
31
|
+
it('calls onChange handler', async () => {
|
|
32
|
+
renderTextArea({ ...tc.props });
|
|
33
|
+
await userEvent.type(screen.getByTestId(tc.testId), 'trigger');
|
|
34
|
+
expect(tc.props.onChange).toHaveBeenCalled();
|
|
35
|
+
});
|
|
36
|
+
it('calls the onKeyUp handler', async () => {
|
|
37
|
+
renderTextArea({ ...tc.props });
|
|
38
|
+
await userEvent.type(screen.getByTestId(tc.testId), 'trigger');
|
|
39
|
+
expect(tc.props.onKeyUp).toHaveBeenCalled();
|
|
40
|
+
});
|
|
41
|
+
it('respects readOnly', async () => {
|
|
42
|
+
renderTextArea({ ...tc.props, disabled: true });
|
|
43
|
+
expect(screen.getByTestId(tc.testId)).not.toBeEnabled();
|
|
44
|
+
});
|
|
45
|
+
it('provides rows', async () => {
|
|
46
|
+
renderTextArea({ ...tc.props, rows: 7 });
|
|
47
|
+
expect(screen.getByTestId(tc.testId)).toHaveProperty('rows', 7);
|
|
48
|
+
});
|
|
49
|
+
it('provides cols', async () => {
|
|
50
|
+
renderTextArea({ ...tc.props, cols: 8 });
|
|
51
|
+
expect(screen.getByTestId(tc.testId)).toHaveProperty('cols', 8);
|
|
52
|
+
});
|
|
53
|
+
it('provides name', async () => {
|
|
54
|
+
const name = 'myTextArea';
|
|
55
|
+
renderTextArea({ ...tc.props, name });
|
|
56
|
+
expect(screen.getByTestId(tc.testId)).toHaveProperty('name', name);
|
|
57
|
+
});
|
|
58
|
+
it('provides id', async () => {
|
|
59
|
+
const id = 'myTextId';
|
|
60
|
+
renderTextArea({ ...tc.props, id });
|
|
61
|
+
expect(screen.getByTestId(tc.testId)).toHaveProperty('id', id);
|
|
62
|
+
});
|
|
63
|
+
it('provides extraClassNames', async () => {
|
|
64
|
+
const extraClassNames = 'extra css';
|
|
65
|
+
renderTextArea({ ...tc.props, extraClassNames });
|
|
66
|
+
expect(screen.getByTestId(tc.testId)).toHaveClass(extraClassNames);
|
|
67
|
+
});
|
|
68
|
+
it('forwards ref to textarea element', () => {
|
|
69
|
+
const ref = React.createRef();
|
|
70
|
+
renderTextArea({ ...tc.props, ref });
|
|
71
|
+
expect(ref.current).toBeInstanceOf(HTMLTextAreaElement);
|
|
72
|
+
expect(ref.current).toBe(screen.getByTestId(tc.testId));
|
|
73
|
+
});
|
|
74
|
+
it('allows programmatic focus via ref', () => {
|
|
75
|
+
const ref = React.createRef();
|
|
76
|
+
renderTextArea({ ...tc.props, ref });
|
|
77
|
+
ref.current?.focus();
|
|
78
|
+
expect(ref.current).toHaveFocus();
|
|
79
|
+
});
|
|
80
|
+
it('allows programmatic text selection via ref', () => {
|
|
81
|
+
const ref = React.createRef();
|
|
82
|
+
const value = 'Test text content';
|
|
83
|
+
renderTextArea({ ...tc.props, ref, value });
|
|
84
|
+
ref.current?.setSelectionRange(5, 9); // Select "text"
|
|
85
|
+
expect(ref.current?.selectionStart).toBe(5);
|
|
86
|
+
expect(ref.current?.selectionEnd).toBe(9);
|
|
87
|
+
});
|
|
88
|
+
it('works with callback ref', () => {
|
|
89
|
+
let refElement = null;
|
|
90
|
+
const callbackRef = (element) => {
|
|
91
|
+
refElement = element;
|
|
92
|
+
};
|
|
93
|
+
renderTextArea({ ...tc.props, ref: callbackRef });
|
|
94
|
+
expect(refElement).toBeInstanceOf(HTMLTextAreaElement);
|
|
95
|
+
expect(refElement).toBe(screen.getByTestId(tc.testId));
|
|
96
|
+
});
|
|
97
|
+
it('calls onSelectionChange on mouseup event', async () => {
|
|
98
|
+
const onSelectionChange = jest.fn();
|
|
99
|
+
renderTextArea({ ...tc.props, onSelectionChange, value: 'Test content' });
|
|
100
|
+
const textarea = screen.getByTestId(tc.testId);
|
|
101
|
+
await userEvent.click(textarea);
|
|
102
|
+
expect(onSelectionChange).toHaveBeenCalled();
|
|
103
|
+
expect(onSelectionChange).toHaveBeenCalledWith(expect.any(Event));
|
|
104
|
+
});
|
|
105
|
+
it('calls onSelectionChange on keyup event', async () => {
|
|
106
|
+
const onSelectionChange = jest.fn();
|
|
107
|
+
renderTextArea({ ...tc.props, onSelectionChange, value: 'Test content' });
|
|
108
|
+
const textarea = screen.getByTestId(tc.testId);
|
|
109
|
+
await userEvent.type(textarea, '{arrowleft}');
|
|
110
|
+
expect(onSelectionChange).toHaveBeenCalled();
|
|
111
|
+
expect(onSelectionChange).toHaveBeenCalledWith(expect.any(Event));
|
|
112
|
+
});
|
|
113
|
+
it('does not throw error when onSelectionChange is not provided', async () => {
|
|
114
|
+
expect(() => {
|
|
115
|
+
renderTextArea({ ...tc.props, value: 'Test content' });
|
|
116
|
+
const textarea = screen.getByTestId(tc.testId);
|
|
117
|
+
userEvent.click(textarea);
|
|
118
|
+
}).not.toThrow();
|
|
119
|
+
});
|
|
120
|
+
it('provides correct event object to onSelectionChange', async () => {
|
|
121
|
+
const onSelectionChange = jest.fn();
|
|
122
|
+
renderTextArea({ ...tc.props, onSelectionChange, value: 'Test content' });
|
|
123
|
+
const textarea = screen.getByTestId(tc.testId);
|
|
124
|
+
await userEvent.click(textarea);
|
|
125
|
+
const eventArg = onSelectionChange.mock.calls[0][0];
|
|
126
|
+
expect(eventArg).toBeInstanceOf(Event);
|
|
127
|
+
expect(eventArg.target).toBe(textarea);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TextArea as default } from './TextArea';
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import '../styles.css';
|
|
4
|
+
import { setValidInputDimension } from '../utils/validateStyleDimension';
|
|
5
|
+
import { getQTipData } from '../Tooltip/qTip.utilities';
|
|
6
|
+
const errorClasses = 'tw-border-sq-danger-color';
|
|
7
|
+
const borderColorClasses = [
|
|
8
|
+
'tw-border-sq-disabled-gray',
|
|
9
|
+
'dark:tw-border-sq-dark-disabled-gray',
|
|
10
|
+
'dark:focus:tw-border-sq-color-dark-dark',
|
|
11
|
+
'dark:active:tw-border-sq-color-dark-dark',
|
|
12
|
+
'focus:tw-border-sq-color-dark',
|
|
13
|
+
'active:tw-border-sq-color-dark',
|
|
14
|
+
].join(' ');
|
|
15
|
+
const baseClasses = 'tw-h-inputs tw-leading-normal tw-outline-none tw-py-1 tw-px-3' +
|
|
16
|
+
' disabled:tw-pointer-events-none disabled:tw-bg-sq-light-gray disabled:dark:tw-bg-sq-dark-disabled-gray' +
|
|
17
|
+
' disabled:tw-cursor-not-allowed tw-p-1 tw-border-solid tw-border tw-placeholder-gray-400' +
|
|
18
|
+
' dark:tw-placeholder-sq-dark-text-lighter specTextField';
|
|
19
|
+
const darkTheme = 'dark:tw-bg-sq-dark-background dark:tw-text-sq-dark-text disabled:dark:tw-text-sq-dark-text-lighter';
|
|
20
|
+
const lightTheme = 'tw-text-sq-text-color disabled:tw-text-sq-darkish-gray';
|
|
21
|
+
const sizeClasses = {
|
|
22
|
+
sm: 'tw-text-sm',
|
|
23
|
+
lg: 'tw-text-xl',
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Textfield.
|
|
27
|
+
*/
|
|
28
|
+
export const TextField = React.forwardRef((props, ref) => {
|
|
29
|
+
const { readonly = false, disabled = false, onChange, onKeyUp, onFocus, onBlur, onKeyDown, id, name, size = 'sm', value, placeholder, extraClassNames, testId, type = 'text', inputGroup, step, showError, errorText, min, max, maxLength, minLength, required = false, autoComplete = 'off', inputWidth = undefined, inputHeight = undefined, autoFocus = false, ...tooltipProps } = props;
|
|
30
|
+
const internalRef = useRef(null);
|
|
31
|
+
const [cursor, setCursor] = useState(null);
|
|
32
|
+
const tooltipData = getQTipData(tooltipProps);
|
|
33
|
+
const setAllRefs = (receivedRef) => {
|
|
34
|
+
if (ref)
|
|
35
|
+
ref.current = receivedRef;
|
|
36
|
+
internalRef.current = receivedRef;
|
|
37
|
+
};
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (autoFocus) {
|
|
40
|
+
setTimeout(() => {
|
|
41
|
+
internalRef.current?.focus();
|
|
42
|
+
}, 0);
|
|
43
|
+
}
|
|
44
|
+
}, []);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const input = internalRef.current;
|
|
47
|
+
if (input && type !== 'number' && type !== 'email')
|
|
48
|
+
input.setSelectionRange(cursor, cursor);
|
|
49
|
+
}, [ref, cursor, value]);
|
|
50
|
+
const handleChange = (e) => {
|
|
51
|
+
setCursor(e.target.selectionStart);
|
|
52
|
+
onChange && onChange(e);
|
|
53
|
+
};
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
/**
|
|
56
|
+
* we need to change the value only if it's different since the internal state of "input" will change it anyway
|
|
57
|
+
* this will only be the case when the value has been changed externally via store (undo / redo)
|
|
58
|
+
*/
|
|
59
|
+
if (value !== null && value !== undefined && value !== internalRef.current?.value && internalRef.current) {
|
|
60
|
+
// we need to use this method because using the value props directly will switch the input to a "controlled"
|
|
61
|
+
// component
|
|
62
|
+
internalRef.current.value = `${value}`;
|
|
63
|
+
}
|
|
64
|
+
}, [value]);
|
|
65
|
+
let borderRadius = 'tw-rounded-md';
|
|
66
|
+
if (inputGroup === 'left') {
|
|
67
|
+
borderRadius = 'tw-rounded-l-md tw-border-r-0 focus:tw-border-r' + ' active:tw-border-r';
|
|
68
|
+
}
|
|
69
|
+
else if (inputGroup === 'right') {
|
|
70
|
+
borderRadius = 'tw-rounded-r-md tw-border-l-0 focus:tw-border-l active:tw-border-l';
|
|
71
|
+
}
|
|
72
|
+
const appliedClasses = `${baseClasses} ${sizeClasses[size]} ${extraClassNames} ${lightTheme} ${darkTheme} ${borderRadius} ${showError ? errorClasses : borderColorClasses} `;
|
|
73
|
+
const inputProp = setValidInputDimension(inputWidth, inputHeight)
|
|
74
|
+
? {
|
|
75
|
+
style: setValidInputDimension(inputWidth, inputHeight),
|
|
76
|
+
}
|
|
77
|
+
: {};
|
|
78
|
+
const inputLenghtProp = {};
|
|
79
|
+
if (maxLength)
|
|
80
|
+
inputLenghtProp.maxLength = maxLength;
|
|
81
|
+
if (minLength)
|
|
82
|
+
inputLenghtProp.minLength = minLength;
|
|
83
|
+
return (_jsxs(_Fragment, { children: [
|
|
84
|
+
_jsx("input", { ref: setAllRefs, "data-testid": testId, name: name, id: id, type: type, value: value, className: appliedClasses, placeholder: placeholder, disabled: disabled, readOnly: readonly, autoComplete: autoComplete, onChange: handleChange, onFocus: onFocus, onBlur: onBlur, onKeyDown: onKeyDown, onKeyUp: onKeyUp, step: step, required: required, min: min, max: max, ...inputLenghtProp, ...inputProp, ...tooltipData }), errorText && showError && _jsx("div", { className: "tw-text-sq-danger-color tw-text-xs tw-mt-1", children: errorText })] }));
|
|
85
|
+
});
|