@sproutsocial/seeds-react-segmented-control 1.0.0

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/.eslintignore ADDED
@@ -0,0 +1,6 @@
1
+ # Node modules
2
+ node_modules/
3
+
4
+ # Build output
5
+ dist/
6
+ coverage/
package/.eslintrc.js ADDED
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ root: true,
3
+ extends: ["eslint-config-seeds/racine"],
4
+ };
@@ -0,0 +1,21 @@
1
+ yarn run v1.22.22
2
+ $ tsup --dts
3
+ CLI Building entry: src/index.ts
4
+ CLI Using tsconfig: tsconfig.json
5
+ CLI tsup v8.0.2
6
+ CLI Using tsup config: /home/runner/work/seeds/seeds/seeds-react/seeds-react-segmented-control/tsup.config.ts
7
+ CLI Target: es2022
8
+ CLI Cleaning output folder
9
+ CJS Build start
10
+ ESM Build start
11
+ CJS dist/index.js 6.72 KB
12
+ CJS dist/index.js.map 8.90 KB
13
+ CJS ⚡️ Build success in 129ms
14
+ ESM dist/esm/index.js 4.47 KB
15
+ ESM dist/esm/index.js.map 8.81 KB
16
+ ESM ⚡️ Build success in 134ms
17
+ DTS Build start
18
+ DTS ⚡️ Build success in 23224ms
19
+ DTS dist/index.d.ts 1.47 KB
20
+ DTS dist/index.d.mts 1.47 KB
21
+ Done in 30.91s.
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @sproutsocial/seeds-react-segmented-control
2
+
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - d480e33: Migrated SegmentedControl to its own seeds-react-segmented-control
@@ -0,0 +1,170 @@
1
+ // src/SegmentedControl.tsx
2
+ import * as React from "react";
3
+ import { useState, useContext } from "react";
4
+ import Box2 from "@sproutsocial/seeds-react-box";
5
+
6
+ // src/styles.ts
7
+ import styled, { css } from "styled-components";
8
+ import {
9
+ visuallyHidden,
10
+ focusRing,
11
+ disabled
12
+ } from "@sproutsocial/seeds-react-mixins";
13
+ import Box from "@sproutsocial/seeds-react-box";
14
+ import Text from "@sproutsocial/seeds-react-text";
15
+ var SegmentedControlContainer = styled(Box)`
16
+ border: 1px solid
17
+ ${(props) => props.theme.colors.button.secondary.border.base};
18
+ border-radius: ${(props) => props.theme.radii.outer};
19
+ padding: ${(props) => props.theme.space[100]};
20
+
21
+ legend {
22
+ ${visuallyHidden}
23
+ }
24
+
25
+ ${(props) => props.disabled && disabled}
26
+ `;
27
+ var SegmentedControlItemContainer = styled(Box)`
28
+ flex: 1 1 auto;
29
+ display: flex;
30
+ cursor: pointer;
31
+
32
+ & + & {
33
+ margin-left: ${(props) => props.theme.space[100]};
34
+ }
35
+
36
+ &:focus-within label {
37
+ ${focusRing}
38
+ }
39
+
40
+ input {
41
+ ${visuallyHidden}
42
+ }
43
+
44
+ ${(props) => props.disabled && disabled}
45
+ `;
46
+ var SegmentedControlLabel = styled(Text)`
47
+ flex: 1 1 auto;
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: center;
51
+ text-align: center;
52
+ color: ${(props) => props.theme.colors.text.body};
53
+ cursor: pointer;
54
+ font-size: ${(props) => props.theme.typography[200].fontSize};
55
+ /**
56
+ * Matches default line height of Icon. Also matches the overall height of
57
+ * Input, Select, and Button.
58
+ */
59
+ line-height: 16px;
60
+ font-weight: ${(props) => props.theme.fontWeights.semibold};
61
+
62
+ border-radius: ${(props) => props.theme.radii.inner};
63
+ /* To match the height of buttons... 350 padding - 2px top and bottom padding of the parent - 1px border on top and bottom */
64
+ padding: calc(${(props) => props.theme.space[350]} - 6px);
65
+ transition: all ${(props) => props.theme.duration.fast};
66
+
67
+ &:hover {
68
+ background-color: ${(props) => props.theme.colors.listItem.background.hover};
69
+ }
70
+
71
+ ${(props) => props.isActive && css`
72
+ color: ${(props2) => props2.theme.colors.text.inverse};
73
+ background-color: ${(props2) => props2.theme.colors.listItem.background.selected};
74
+
75
+ &:hover {
76
+ background-color: ${(props2) => props2.theme.colors.listItem.background.selected};
77
+ }
78
+ `}
79
+ `;
80
+
81
+ // src/SegmentedControl.tsx
82
+ import { jsx, jsxs } from "react/jsx-runtime";
83
+ var nameCounter = 0;
84
+ var idCounter = 0;
85
+ var SegmentedControlContext = React.createContext(null);
86
+ var SegmentedControlItem = ({
87
+ value,
88
+ children,
89
+ disabled: disabled2,
90
+ ...rest
91
+ }) => {
92
+ const context = useContext(SegmentedControlContext);
93
+ if (!context) {
94
+ return null;
95
+ }
96
+ const { name, selectedValue, onChange } = context;
97
+ const [id] = useState(`${name}-${idCounter++}`);
98
+ const isChecked = value === selectedValue;
99
+ return /* @__PURE__ */ jsxs(
100
+ SegmentedControlItemContainer,
101
+ {
102
+ "data-segmentedcontrol-isactive": isChecked,
103
+ "data-qa-segmentedcontrol-item": value,
104
+ "data-qa-segmentedcontrol-ischecked": isChecked === true,
105
+ disabled: disabled2,
106
+ ...rest,
107
+ children: [
108
+ /* @__PURE__ */ jsx(
109
+ "input",
110
+ {
111
+ type: "radio",
112
+ id,
113
+ name,
114
+ value,
115
+ checked: isChecked,
116
+ onChange,
117
+ disabled: disabled2
118
+ }
119
+ ),
120
+ /* @__PURE__ */ jsx(SegmentedControlLabel, { as: "label", htmlFor: id, isActive: isChecked, children })
121
+ ]
122
+ }
123
+ );
124
+ };
125
+ var SegmentedControl = ({
126
+ selectedValue,
127
+ label,
128
+ onChange,
129
+ children,
130
+ ...rest
131
+ }) => {
132
+ const [name] = useState(`Racine-segmented-control-${nameCounter++}`);
133
+ return /* @__PURE__ */ jsx(
134
+ SegmentedControlContext.Provider,
135
+ {
136
+ value: {
137
+ name,
138
+ selectedValue,
139
+ onChange
140
+ },
141
+ children: /* @__PURE__ */ jsxs(
142
+ SegmentedControlContainer,
143
+ {
144
+ forwardedAs: "fieldset",
145
+ "data-qa-segmentedcontrol": label,
146
+ "data-qa-segmentedcontrol-value": selectedValue,
147
+ ...rest,
148
+ children: [
149
+ /* @__PURE__ */ jsx("legend", { children: label }),
150
+ /* @__PURE__ */ jsx(Box2, { display: "flex", children })
151
+ ]
152
+ }
153
+ )
154
+ }
155
+ );
156
+ };
157
+ SegmentedControlItem.displayName = "SegmentedControl.Item";
158
+ SegmentedControl.Item = SegmentedControlItem;
159
+ var SegmentedControl_default = SegmentedControl;
160
+
161
+ // src/SegmentedControlTypes.ts
162
+ import "react";
163
+
164
+ // src/index.ts
165
+ var src_default = SegmentedControl_default;
166
+ export {
167
+ SegmentedControl_default as SegmentedControl,
168
+ src_default as default
169
+ };
170
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/SegmentedControl.tsx","../../src/styles.ts","../../src/SegmentedControlTypes.ts","../../src/index.ts"],"sourcesContent":["import * as React from \"react\";\nimport { useState, useContext } from \"react\";\nimport Box from \"@sproutsocial/seeds-react-box\";\nimport {\n SegmentedControlContainer,\n SegmentedControlItemContainer,\n SegmentedControlLabel,\n} from \"./styles\";\nimport type {\n TypeSegmentedControlProps,\n TypeSegmentedControlItemProps,\n} from \"./SegmentedControlTypes\";\n\nlet nameCounter = 0;\nlet idCounter = 0;\n\ninterface TypeSegmentedControlContext {\n name: string;\n selectedValue: string;\n onChange: (e: React.SyntheticEvent<HTMLInputElement>) => void;\n}\n\nconst SegmentedControlContext = React.createContext<\n TypeSegmentedControlContext | null | undefined\n>(null);\n\nconst SegmentedControlItem = ({\n value,\n children,\n disabled,\n ...rest\n}: TypeSegmentedControlItemProps) => {\n const context = useContext(SegmentedControlContext);\n\n if (!context) {\n return null;\n }\n\n const { name, selectedValue, onChange } = context;\n\n /* eslint-disable-next-line */\n const [id] = useState(`${name}-${idCounter++}`);\n const isChecked = value === selectedValue;\n\n return (\n <SegmentedControlItemContainer\n data-segmentedcontrol-isactive={isChecked}\n data-qa-segmentedcontrol-item={value}\n data-qa-segmentedcontrol-ischecked={isChecked === true}\n disabled={disabled}\n {...rest}\n >\n <input\n type=\"radio\"\n id={id}\n name={name}\n value={value}\n checked={isChecked}\n onChange={onChange}\n disabled={disabled}\n />\n <SegmentedControlLabel as=\"label\" htmlFor={id} isActive={isChecked}>\n {children}\n </SegmentedControlLabel>\n </SegmentedControlItemContainer>\n );\n};\n\nconst SegmentedControl = ({\n selectedValue,\n label,\n onChange,\n children,\n ...rest\n}: TypeSegmentedControlProps) => {\n const [name] = useState(`Racine-segmented-control-${nameCounter++}`);\n return (\n <SegmentedControlContext.Provider\n value={{\n name,\n selectedValue,\n onChange,\n }}\n >\n <SegmentedControlContainer\n forwardedAs=\"fieldset\"\n data-qa-segmentedcontrol={label}\n data-qa-segmentedcontrol-value={selectedValue}\n {...rest}\n >\n <legend>{label}</legend>\n\n <Box display=\"flex\">{children}</Box>\n </SegmentedControlContainer>\n </SegmentedControlContext.Provider>\n );\n};\n\nSegmentedControlItem.displayName = \"SegmentedControl.Item\";\nSegmentedControl.Item = SegmentedControlItem;\n\nexport default SegmentedControl;\n","import styled, { css } from \"styled-components\";\nimport {\n visuallyHidden,\n focusRing,\n disabled,\n} from \"@sproutsocial/seeds-react-mixins\";\nimport Box from \"@sproutsocial/seeds-react-box\";\nimport Text from \"@sproutsocial/seeds-react-text\";\nimport type {\n TypeSegmentedControlProps,\n TypeSegmentedControlItemProps,\n} from \"./SegmentedControlTypes\";\n\nexport const SegmentedControlContainer = styled(Box)<\n Pick<TypeSegmentedControlProps, \"disabled\">\n>`\n border: 1px solid\n ${(props) => props.theme.colors.button.secondary.border.base};\n border-radius: ${(props) => props.theme.radii.outer};\n padding: ${(props) => props.theme.space[100]};\n\n legend {\n ${visuallyHidden}\n }\n\n ${(props) => props.disabled && disabled}\n`;\n\nexport const SegmentedControlItemContainer = styled(Box)<\n Pick<TypeSegmentedControlItemProps, \"disabled\">\n>`\n flex: 1 1 auto;\n display: flex;\n cursor: pointer;\n\n & + & {\n margin-left: ${(props) => props.theme.space[100]};\n }\n\n &:focus-within label {\n ${focusRing}\n }\n\n input {\n ${visuallyHidden}\n }\n\n ${(props) => props.disabled && disabled}\n`;\n\ninterface TypeSegmentedControlState {\n isActive: boolean;\n}\n\nexport const SegmentedControlLabel = styled(Text)<TypeSegmentedControlState>`\n flex: 1 1 auto;\n display: flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n color: ${(props) => props.theme.colors.text.body};\n cursor: pointer;\n font-size: ${(props) => props.theme.typography[200].fontSize};\n /**\n\t* Matches default line height of Icon. Also matches the overall height of\n\t* Input, Select, and Button.\n\t*/\n line-height: 16px;\n font-weight: ${(props) => props.theme.fontWeights.semibold};\n\n border-radius: ${(props) => props.theme.radii.inner};\n /* To match the height of buttons... 350 padding - 2px top and bottom padding of the parent - 1px border on top and bottom */\n padding: calc(${(props) => props.theme.space[350]} - 6px);\n transition: all ${(props) => props.theme.duration.fast};\n\n &:hover {\n background-color: ${(props) =>\n props.theme.colors.listItem.background.hover};\n }\n\n ${(props) =>\n props.isActive &&\n css`\n color: ${(props) => props.theme.colors.text.inverse};\n background-color: ${(props) =>\n props.theme.colors.listItem.background.selected};\n\n &:hover {\n background-color: ${(props) =>\n props.theme.colors.listItem.background.selected};\n }\n `}\n`;\n","import type { TypeContainerProps } from \"@sproutsocial/seeds-react-box\";\nimport * as React from \"react\";\n\nexport interface TypeSegmentedControlItemProps {\n /** The value of this item. Should be unique among sibling items. */\n value: string;\n children: React.ReactNode;\n /** Disables user action and applies a disabled style on the component */\n disabled?: boolean;\n}\n\nexport interface TypeSegmentedControlProps extends TypeContainerProps {\n /** The value of the currently selected item. Should match the value prop of one of the child items */\n selectedValue: string;\n\n /** The title of the segmented control, used for accessibility purposes */\n label: string;\n\n /** Called when the user selects a new item. You can access the value of the newly selected item using \"event.target.value\" */\n onChange: (e: React.SyntheticEvent<HTMLInputElement>) => void;\n\n /** Disables user action and applies a disabled style on the component */\n disabled?: boolean;\n children: React.ReactNode;\n}\n","import SegmentedControl from \"./SegmentedControl\";\n\nexport default SegmentedControl;\nexport { SegmentedControl };\nexport * from \"./SegmentedControlTypes\";\n"],"mappings":";AAAA,YAAY,WAAW;AACvB,SAAS,UAAU,kBAAkB;AACrC,OAAOA,UAAS;;;ACFhB,OAAO,UAAU,WAAW;AAC5B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,OAAO,SAAS;AAChB,OAAO,UAAU;AAMV,IAAM,4BAA4B,OAAO,GAAG;AAAA;AAAA,MAI7C,CAAC,UAAU,MAAM,MAAM,OAAO,OAAO,UAAU,OAAO,IAAI;AAAA,mBAC7C,CAAC,UAAU,MAAM,MAAM,MAAM,KAAK;AAAA,aACxC,CAAC,UAAU,MAAM,MAAM,MAAM,GAAG,CAAC;AAAA;AAAA;AAAA,MAGxC,cAAc;AAAA;AAAA;AAAA,IAGhB,CAAC,UAAU,MAAM,YAAY,QAAQ;AAAA;AAGlC,IAAM,gCAAgC,OAAO,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAQpC,CAAC,UAAU,MAAM,MAAM,MAAM,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA,MAI9C,SAAS;AAAA;AAAA;AAAA;AAAA,MAIT,cAAc;AAAA;AAAA;AAAA,IAGhB,CAAC,UAAU,MAAM,YAAY,QAAQ;AAAA;AAOlC,IAAM,wBAAwB,OAAO,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAMrC,CAAC,UAAU,MAAM,MAAM,OAAO,KAAK,IAAI;AAAA;AAAA,eAEnC,CAAC,UAAU,MAAM,MAAM,WAAW,GAAG,EAAE,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAM7C,CAAC,UAAU,MAAM,MAAM,YAAY,QAAQ;AAAA;AAAA,mBAEzC,CAAC,UAAU,MAAM,MAAM,MAAM,KAAK;AAAA;AAAA,kBAEnC,CAAC,UAAU,MAAM,MAAM,MAAM,GAAG,CAAC;AAAA,oBAC/B,CAAC,UAAU,MAAM,MAAM,SAAS,IAAI;AAAA;AAAA;AAAA,wBAGhC,CAAC,UACnB,MAAM,MAAM,OAAO,SAAS,WAAW,KAAK;AAAA;AAAA;AAAA,IAG9C,CAAC,UACD,MAAM,YACN;AAAA,eACW,CAACC,WAAUA,OAAM,MAAM,OAAO,KAAK,OAAO;AAAA,0BAC/B,CAACA,WACnBA,OAAM,MAAM,OAAO,SAAS,WAAW,QAAQ;AAAA;AAAA;AAAA,4BAG3B,CAACA,WACnBA,OAAM,MAAM,OAAO,SAAS,WAAW,QAAQ;AAAA;AAAA,KAEpD;AAAA;;;AD9CD,SAOE,KAPF;AAhCJ,IAAI,cAAc;AAClB,IAAI,YAAY;AAQhB,IAAM,0BAAgC,oBAEpC,IAAI;AAEN,IAAM,uBAAuB,CAAC;AAAA,EAC5B;AAAA,EACA;AAAA,EACA,UAAAC;AAAA,EACA,GAAG;AACL,MAAqC;AACnC,QAAM,UAAU,WAAW,uBAAuB;AAElD,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AAEA,QAAM,EAAE,MAAM,eAAe,SAAS,IAAI;AAG1C,QAAM,CAAC,EAAE,IAAI,SAAS,GAAG,IAAI,IAAI,WAAW,EAAE;AAC9C,QAAM,YAAY,UAAU;AAE5B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,kCAAgC;AAAA,MAChC,iCAA+B;AAAA,MAC/B,sCAAoC,cAAc;AAAA,MAClD,UAAUA;AAAA,MACT,GAAG;AAAA,MAEJ;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL;AAAA,YACA;AAAA,YACA;AAAA,YACA,SAAS;AAAA,YACT;AAAA,YACA,UAAUA;AAAA;AAAA,QACZ;AAAA,QACA,oBAAC,yBAAsB,IAAG,SAAQ,SAAS,IAAI,UAAU,WACtD,UACH;AAAA;AAAA;AAAA,EACF;AAEJ;AAEA,IAAM,mBAAmB,CAAC;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAAiC;AAC/B,QAAM,CAAC,IAAI,IAAI,SAAS,4BAA4B,aAAa,EAAE;AACnE,SACE;AAAA,IAAC,wBAAwB;AAAA,IAAxB;AAAA,MACC,OAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MAEA;AAAA,QAAC;AAAA;AAAA,UACC,aAAY;AAAA,UACZ,4BAA0B;AAAA,UAC1B,kCAAgC;AAAA,UAC/B,GAAG;AAAA,UAEJ;AAAA,gCAAC,YAAQ,iBAAM;AAAA,YAEf,oBAACC,MAAA,EAAI,SAAQ,QAAQ,UAAS;AAAA;AAAA;AAAA,MAChC;AAAA;AAAA,EACF;AAEJ;AAEA,qBAAqB,cAAc;AACnC,iBAAiB,OAAO;AAExB,IAAO,2BAAQ;;;AEpGf,OAAuB;;;ACCvB,IAAO,cAAQ;","names":["Box","props","disabled","Box"]}
@@ -0,0 +1,32 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { TypeContainerProps } from '@sproutsocial/seeds-react-box';
3
+ import * as React from 'react';
4
+
5
+ interface TypeSegmentedControlItemProps {
6
+ /** The value of this item. Should be unique among sibling items. */
7
+ value: string;
8
+ children: React.ReactNode;
9
+ /** Disables user action and applies a disabled style on the component */
10
+ disabled?: boolean;
11
+ }
12
+ interface TypeSegmentedControlProps extends TypeContainerProps {
13
+ /** The value of the currently selected item. Should match the value prop of one of the child items */
14
+ selectedValue: string;
15
+ /** The title of the segmented control, used for accessibility purposes */
16
+ label: string;
17
+ /** Called when the user selects a new item. You can access the value of the newly selected item using "event.target.value" */
18
+ onChange: (e: React.SyntheticEvent<HTMLInputElement>) => void;
19
+ /** Disables user action and applies a disabled style on the component */
20
+ disabled?: boolean;
21
+ children: React.ReactNode;
22
+ }
23
+
24
+ declare const SegmentedControl: {
25
+ ({ selectedValue, label, onChange, children, ...rest }: TypeSegmentedControlProps): react_jsx_runtime.JSX.Element;
26
+ Item: {
27
+ ({ value, children, disabled, ...rest }: TypeSegmentedControlItemProps): react_jsx_runtime.JSX.Element | null;
28
+ displayName: string;
29
+ };
30
+ };
31
+
32
+ export { SegmentedControl, type TypeSegmentedControlItemProps, type TypeSegmentedControlProps, SegmentedControl as default };
@@ -0,0 +1,32 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { TypeContainerProps } from '@sproutsocial/seeds-react-box';
3
+ import * as React from 'react';
4
+
5
+ interface TypeSegmentedControlItemProps {
6
+ /** The value of this item. Should be unique among sibling items. */
7
+ value: string;
8
+ children: React.ReactNode;
9
+ /** Disables user action and applies a disabled style on the component */
10
+ disabled?: boolean;
11
+ }
12
+ interface TypeSegmentedControlProps extends TypeContainerProps {
13
+ /** The value of the currently selected item. Should match the value prop of one of the child items */
14
+ selectedValue: string;
15
+ /** The title of the segmented control, used for accessibility purposes */
16
+ label: string;
17
+ /** Called when the user selects a new item. You can access the value of the newly selected item using "event.target.value" */
18
+ onChange: (e: React.SyntheticEvent<HTMLInputElement>) => void;
19
+ /** Disables user action and applies a disabled style on the component */
20
+ disabled?: boolean;
21
+ children: React.ReactNode;
22
+ }
23
+
24
+ declare const SegmentedControl: {
25
+ ({ selectedValue, label, onChange, children, ...rest }: TypeSegmentedControlProps): react_jsx_runtime.JSX.Element;
26
+ Item: {
27
+ ({ value, children, disabled, ...rest }: TypeSegmentedControlItemProps): react_jsx_runtime.JSX.Element | null;
28
+ displayName: string;
29
+ };
30
+ };
31
+
32
+ export { SegmentedControl, type TypeSegmentedControlItemProps, type TypeSegmentedControlProps, SegmentedControl as default };
package/dist/index.js ADDED
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ SegmentedControl: () => SegmentedControl_default,
34
+ default: () => src_default
35
+ });
36
+ module.exports = __toCommonJS(src_exports);
37
+
38
+ // src/SegmentedControl.tsx
39
+ var React = __toESM(require("react"));
40
+ var import_react = require("react");
41
+ var import_seeds_react_box2 = __toESM(require("@sproutsocial/seeds-react-box"));
42
+
43
+ // src/styles.ts
44
+ var import_styled_components = __toESM(require("styled-components"));
45
+ var import_seeds_react_mixins = require("@sproutsocial/seeds-react-mixins");
46
+ var import_seeds_react_box = __toESM(require("@sproutsocial/seeds-react-box"));
47
+ var import_seeds_react_text = __toESM(require("@sproutsocial/seeds-react-text"));
48
+ var SegmentedControlContainer = (0, import_styled_components.default)(import_seeds_react_box.default)`
49
+ border: 1px solid
50
+ ${(props) => props.theme.colors.button.secondary.border.base};
51
+ border-radius: ${(props) => props.theme.radii.outer};
52
+ padding: ${(props) => props.theme.space[100]};
53
+
54
+ legend {
55
+ ${import_seeds_react_mixins.visuallyHidden}
56
+ }
57
+
58
+ ${(props) => props.disabled && import_seeds_react_mixins.disabled}
59
+ `;
60
+ var SegmentedControlItemContainer = (0, import_styled_components.default)(import_seeds_react_box.default)`
61
+ flex: 1 1 auto;
62
+ display: flex;
63
+ cursor: pointer;
64
+
65
+ & + & {
66
+ margin-left: ${(props) => props.theme.space[100]};
67
+ }
68
+
69
+ &:focus-within label {
70
+ ${import_seeds_react_mixins.focusRing}
71
+ }
72
+
73
+ input {
74
+ ${import_seeds_react_mixins.visuallyHidden}
75
+ }
76
+
77
+ ${(props) => props.disabled && import_seeds_react_mixins.disabled}
78
+ `;
79
+ var SegmentedControlLabel = (0, import_styled_components.default)(import_seeds_react_text.default)`
80
+ flex: 1 1 auto;
81
+ display: flex;
82
+ align-items: center;
83
+ justify-content: center;
84
+ text-align: center;
85
+ color: ${(props) => props.theme.colors.text.body};
86
+ cursor: pointer;
87
+ font-size: ${(props) => props.theme.typography[200].fontSize};
88
+ /**
89
+ * Matches default line height of Icon. Also matches the overall height of
90
+ * Input, Select, and Button.
91
+ */
92
+ line-height: 16px;
93
+ font-weight: ${(props) => props.theme.fontWeights.semibold};
94
+
95
+ border-radius: ${(props) => props.theme.radii.inner};
96
+ /* To match the height of buttons... 350 padding - 2px top and bottom padding of the parent - 1px border on top and bottom */
97
+ padding: calc(${(props) => props.theme.space[350]} - 6px);
98
+ transition: all ${(props) => props.theme.duration.fast};
99
+
100
+ &:hover {
101
+ background-color: ${(props) => props.theme.colors.listItem.background.hover};
102
+ }
103
+
104
+ ${(props) => props.isActive && import_styled_components.css`
105
+ color: ${(props2) => props2.theme.colors.text.inverse};
106
+ background-color: ${(props2) => props2.theme.colors.listItem.background.selected};
107
+
108
+ &:hover {
109
+ background-color: ${(props2) => props2.theme.colors.listItem.background.selected};
110
+ }
111
+ `}
112
+ `;
113
+
114
+ // src/SegmentedControl.tsx
115
+ var import_jsx_runtime = require("react/jsx-runtime");
116
+ var nameCounter = 0;
117
+ var idCounter = 0;
118
+ var SegmentedControlContext = React.createContext(null);
119
+ var SegmentedControlItem = ({
120
+ value,
121
+ children,
122
+ disabled: disabled2,
123
+ ...rest
124
+ }) => {
125
+ const context = (0, import_react.useContext)(SegmentedControlContext);
126
+ if (!context) {
127
+ return null;
128
+ }
129
+ const { name, selectedValue, onChange } = context;
130
+ const [id] = (0, import_react.useState)(`${name}-${idCounter++}`);
131
+ const isChecked = value === selectedValue;
132
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
133
+ SegmentedControlItemContainer,
134
+ {
135
+ "data-segmentedcontrol-isactive": isChecked,
136
+ "data-qa-segmentedcontrol-item": value,
137
+ "data-qa-segmentedcontrol-ischecked": isChecked === true,
138
+ disabled: disabled2,
139
+ ...rest,
140
+ children: [
141
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
142
+ "input",
143
+ {
144
+ type: "radio",
145
+ id,
146
+ name,
147
+ value,
148
+ checked: isChecked,
149
+ onChange,
150
+ disabled: disabled2
151
+ }
152
+ ),
153
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SegmentedControlLabel, { as: "label", htmlFor: id, isActive: isChecked, children })
154
+ ]
155
+ }
156
+ );
157
+ };
158
+ var SegmentedControl = ({
159
+ selectedValue,
160
+ label,
161
+ onChange,
162
+ children,
163
+ ...rest
164
+ }) => {
165
+ const [name] = (0, import_react.useState)(`Racine-segmented-control-${nameCounter++}`);
166
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
167
+ SegmentedControlContext.Provider,
168
+ {
169
+ value: {
170
+ name,
171
+ selectedValue,
172
+ onChange
173
+ },
174
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
175
+ SegmentedControlContainer,
176
+ {
177
+ forwardedAs: "fieldset",
178
+ "data-qa-segmentedcontrol": label,
179
+ "data-qa-segmentedcontrol-value": selectedValue,
180
+ ...rest,
181
+ children: [
182
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("legend", { children: label }),
183
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_seeds_react_box2.default, { display: "flex", children })
184
+ ]
185
+ }
186
+ )
187
+ }
188
+ );
189
+ };
190
+ SegmentedControlItem.displayName = "SegmentedControl.Item";
191
+ SegmentedControl.Item = SegmentedControlItem;
192
+ var SegmentedControl_default = SegmentedControl;
193
+
194
+ // src/SegmentedControlTypes.ts
195
+ var React2 = require("react");
196
+
197
+ // src/index.ts
198
+ var src_default = SegmentedControl_default;
199
+ // Annotate the CommonJS export names for ESM import in node:
200
+ 0 && (module.exports = {
201
+ SegmentedControl
202
+ });
203
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/SegmentedControl.tsx","../src/styles.ts","../src/SegmentedControlTypes.ts"],"sourcesContent":["import SegmentedControl from \"./SegmentedControl\";\n\nexport default SegmentedControl;\nexport { SegmentedControl };\nexport * from \"./SegmentedControlTypes\";\n","import * as React from \"react\";\nimport { useState, useContext } from \"react\";\nimport Box from \"@sproutsocial/seeds-react-box\";\nimport {\n SegmentedControlContainer,\n SegmentedControlItemContainer,\n SegmentedControlLabel,\n} from \"./styles\";\nimport type {\n TypeSegmentedControlProps,\n TypeSegmentedControlItemProps,\n} from \"./SegmentedControlTypes\";\n\nlet nameCounter = 0;\nlet idCounter = 0;\n\ninterface TypeSegmentedControlContext {\n name: string;\n selectedValue: string;\n onChange: (e: React.SyntheticEvent<HTMLInputElement>) => void;\n}\n\nconst SegmentedControlContext = React.createContext<\n TypeSegmentedControlContext | null | undefined\n>(null);\n\nconst SegmentedControlItem = ({\n value,\n children,\n disabled,\n ...rest\n}: TypeSegmentedControlItemProps) => {\n const context = useContext(SegmentedControlContext);\n\n if (!context) {\n return null;\n }\n\n const { name, selectedValue, onChange } = context;\n\n /* eslint-disable-next-line */\n const [id] = useState(`${name}-${idCounter++}`);\n const isChecked = value === selectedValue;\n\n return (\n <SegmentedControlItemContainer\n data-segmentedcontrol-isactive={isChecked}\n data-qa-segmentedcontrol-item={value}\n data-qa-segmentedcontrol-ischecked={isChecked === true}\n disabled={disabled}\n {...rest}\n >\n <input\n type=\"radio\"\n id={id}\n name={name}\n value={value}\n checked={isChecked}\n onChange={onChange}\n disabled={disabled}\n />\n <SegmentedControlLabel as=\"label\" htmlFor={id} isActive={isChecked}>\n {children}\n </SegmentedControlLabel>\n </SegmentedControlItemContainer>\n );\n};\n\nconst SegmentedControl = ({\n selectedValue,\n label,\n onChange,\n children,\n ...rest\n}: TypeSegmentedControlProps) => {\n const [name] = useState(`Racine-segmented-control-${nameCounter++}`);\n return (\n <SegmentedControlContext.Provider\n value={{\n name,\n selectedValue,\n onChange,\n }}\n >\n <SegmentedControlContainer\n forwardedAs=\"fieldset\"\n data-qa-segmentedcontrol={label}\n data-qa-segmentedcontrol-value={selectedValue}\n {...rest}\n >\n <legend>{label}</legend>\n\n <Box display=\"flex\">{children}</Box>\n </SegmentedControlContainer>\n </SegmentedControlContext.Provider>\n );\n};\n\nSegmentedControlItem.displayName = \"SegmentedControl.Item\";\nSegmentedControl.Item = SegmentedControlItem;\n\nexport default SegmentedControl;\n","import styled, { css } from \"styled-components\";\nimport {\n visuallyHidden,\n focusRing,\n disabled,\n} from \"@sproutsocial/seeds-react-mixins\";\nimport Box from \"@sproutsocial/seeds-react-box\";\nimport Text from \"@sproutsocial/seeds-react-text\";\nimport type {\n TypeSegmentedControlProps,\n TypeSegmentedControlItemProps,\n} from \"./SegmentedControlTypes\";\n\nexport const SegmentedControlContainer = styled(Box)<\n Pick<TypeSegmentedControlProps, \"disabled\">\n>`\n border: 1px solid\n ${(props) => props.theme.colors.button.secondary.border.base};\n border-radius: ${(props) => props.theme.radii.outer};\n padding: ${(props) => props.theme.space[100]};\n\n legend {\n ${visuallyHidden}\n }\n\n ${(props) => props.disabled && disabled}\n`;\n\nexport const SegmentedControlItemContainer = styled(Box)<\n Pick<TypeSegmentedControlItemProps, \"disabled\">\n>`\n flex: 1 1 auto;\n display: flex;\n cursor: pointer;\n\n & + & {\n margin-left: ${(props) => props.theme.space[100]};\n }\n\n &:focus-within label {\n ${focusRing}\n }\n\n input {\n ${visuallyHidden}\n }\n\n ${(props) => props.disabled && disabled}\n`;\n\ninterface TypeSegmentedControlState {\n isActive: boolean;\n}\n\nexport const SegmentedControlLabel = styled(Text)<TypeSegmentedControlState>`\n flex: 1 1 auto;\n display: flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n color: ${(props) => props.theme.colors.text.body};\n cursor: pointer;\n font-size: ${(props) => props.theme.typography[200].fontSize};\n /**\n\t* Matches default line height of Icon. Also matches the overall height of\n\t* Input, Select, and Button.\n\t*/\n line-height: 16px;\n font-weight: ${(props) => props.theme.fontWeights.semibold};\n\n border-radius: ${(props) => props.theme.radii.inner};\n /* To match the height of buttons... 350 padding - 2px top and bottom padding of the parent - 1px border on top and bottom */\n padding: calc(${(props) => props.theme.space[350]} - 6px);\n transition: all ${(props) => props.theme.duration.fast};\n\n &:hover {\n background-color: ${(props) =>\n props.theme.colors.listItem.background.hover};\n }\n\n ${(props) =>\n props.isActive &&\n css`\n color: ${(props) => props.theme.colors.text.inverse};\n background-color: ${(props) =>\n props.theme.colors.listItem.background.selected};\n\n &:hover {\n background-color: ${(props) =>\n props.theme.colors.listItem.background.selected};\n }\n `}\n`;\n","import type { TypeContainerProps } from \"@sproutsocial/seeds-react-box\";\nimport * as React from \"react\";\n\nexport interface TypeSegmentedControlItemProps {\n /** The value of this item. Should be unique among sibling items. */\n value: string;\n children: React.ReactNode;\n /** Disables user action and applies a disabled style on the component */\n disabled?: boolean;\n}\n\nexport interface TypeSegmentedControlProps extends TypeContainerProps {\n /** The value of the currently selected item. Should match the value prop of one of the child items */\n selectedValue: string;\n\n /** The title of the segmented control, used for accessibility purposes */\n label: string;\n\n /** Called when the user selects a new item. You can access the value of the newly selected item using \"event.target.value\" */\n onChange: (e: React.SyntheticEvent<HTMLInputElement>) => void;\n\n /** Disables user action and applies a disabled style on the component */\n disabled?: boolean;\n children: React.ReactNode;\n}\n"],"mappings":"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","names":["import_seeds_react_box","styled","Box","Text","props","disabled","Box","React"]}
package/jest.config.js ADDED
@@ -0,0 +1,9 @@
1
+ const baseConfig = require("@sproutsocial/seeds-testing");
2
+
3
+ /** * @type {import('jest').Config} */
4
+ const config = {
5
+ ...baseConfig,
6
+ displayName: "seeds-react-segmented-control",
7
+ };
8
+
9
+ module.exports = config;
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@sproutsocial/seeds-react-segmented-control",
3
+ "version": "1.0.0",
4
+ "description": "Seeds React SegmentedControl",
5
+ "author": "Sprout Social, Inc.",
6
+ "license": "MIT",
7
+ "main": "dist/index.js",
8
+ "module": "dist/esm/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "scripts": {
11
+ "build": "tsup --dts",
12
+ "build:debug": "tsup --dts --metafile",
13
+ "dev": "tsup --watch --dts",
14
+ "clean": "rm -rf .turbo dist",
15
+ "clean:modules": "rm -rf node_modules",
16
+ "typecheck": "tsc --noEmit",
17
+ "test": "jest",
18
+ "test:watch": "jest --watch --coverage=false"
19
+ },
20
+ "dependencies": {
21
+ "@sproutsocial/seeds-react-theme": "*",
22
+ "@sproutsocial/seeds-react-system-props": "*",
23
+ "@sproutsocial/seeds-react-box": "*",
24
+ "@sproutsocial/seeds-react-text": "*",
25
+ "@sproutsocial/seeds-react-mixins": "*"
26
+ },
27
+ "devDependencies": {
28
+ "@types/react": "^18.0.0",
29
+ "@types/styled-components": "^5.1.26",
30
+ "@sproutsocial/eslint-config-seeds": "*",
31
+ "react": "^18.0.0",
32
+ "styled-components": "^5.2.3",
33
+ "tsup": "^8.0.2",
34
+ "typescript": "^5.6.2",
35
+ "@sproutsocial/seeds-tsconfig": "*",
36
+ "@sproutsocial/seeds-testing": "*",
37
+ "@sproutsocial/seeds-react-testing-library": "*"
38
+ },
39
+ "peerDependencies": {
40
+ "styled-components": "^5.2.3"
41
+ },
42
+ "engines": {
43
+ "node": ">=18"
44
+ }
45
+ }
@@ -0,0 +1,72 @@
1
+ import React, { useState } from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import Box from "@sproutsocial/seeds-react-box";
4
+ import Button from "@sproutsocial/seeds-react-button";
5
+ import SegmentedControl from "./SegmentedControl";
6
+
7
+ const meta: Meta<typeof SegmentedControl> = {
8
+ title: "Components/SegmentedControl",
9
+ component: SegmentedControl,
10
+ };
11
+ export default meta;
12
+
13
+ type Story = StoryObj<typeof SegmentedControl>;
14
+
15
+ // @ts-ignore IDK what props should be
16
+ const StatefulSegmentedControl = (props) => {
17
+ const [value, setValue] = useState("1");
18
+ return (
19
+ <SegmentedControl
20
+ selectedValue={value}
21
+ label="Segmented control component"
22
+ onChange={(e) => setValue((e.target as HTMLInputElement).value)}
23
+ {...props}
24
+ >
25
+ <SegmentedControl.Item value="1">Test 1</SegmentedControl.Item>
26
+ <SegmentedControl.Item value="2">Test 2</SegmentedControl.Item>
27
+ <SegmentedControl.Item value="3">Test 3</SegmentedControl.Item>
28
+ </SegmentedControl>
29
+ );
30
+ };
31
+
32
+ export const Default: Story = {
33
+ render: () => (
34
+ <Box width={1 / 3} display="flex" alignItems="center" minWidth="25%">
35
+ <StatefulSegmentedControl flex="1" mr={300} />
36
+ <Button appearance="secondary">Some button</Button>
37
+ </Box>
38
+ ),
39
+ };
40
+
41
+ export const Disabled: Story = {
42
+ render: () => (
43
+ <Box width={1 / 3} display="flex" alignItems="center" minWidth="25%">
44
+ <StatefulSegmentedControl flex="1" mr={300} disabled={true} />
45
+ <Button appearance="secondary">Some button</Button>
46
+ </Box>
47
+ ),
48
+ };
49
+
50
+ export const DisabledItem: Story = {
51
+ render: () => {
52
+ const [value, setValue] = useState("1");
53
+ return (
54
+ <Box width={1 / 3} display="flex" alignItems="center" minWidth="25%">
55
+ <SegmentedControl
56
+ selectedValue={value}
57
+ label="Segmented control component"
58
+ onChange={(e) => setValue((e.target as HTMLInputElement).value)}
59
+ flex="1"
60
+ >
61
+ <SegmentedControl.Item value="1">Test 1</SegmentedControl.Item>
62
+ <SegmentedControl.Item value="2" disabled={true}>
63
+ Test 2
64
+ </SegmentedControl.Item>
65
+ <SegmentedControl.Item value="3">Test 3</SegmentedControl.Item>
66
+ <SegmentedControl.Item value="4">Test 4</SegmentedControl.Item>
67
+ <SegmentedControl.Item value="5">Test 5</SegmentedControl.Item>
68
+ </SegmentedControl>
69
+ </Box>
70
+ );
71
+ },
72
+ };
@@ -0,0 +1,102 @@
1
+ import * as React from "react";
2
+ import { useState, useContext } from "react";
3
+ import Box from "@sproutsocial/seeds-react-box";
4
+ import {
5
+ SegmentedControlContainer,
6
+ SegmentedControlItemContainer,
7
+ SegmentedControlLabel,
8
+ } from "./styles";
9
+ import type {
10
+ TypeSegmentedControlProps,
11
+ TypeSegmentedControlItemProps,
12
+ } from "./SegmentedControlTypes";
13
+
14
+ let nameCounter = 0;
15
+ let idCounter = 0;
16
+
17
+ interface TypeSegmentedControlContext {
18
+ name: string;
19
+ selectedValue: string;
20
+ onChange: (e: React.SyntheticEvent<HTMLInputElement>) => void;
21
+ }
22
+
23
+ const SegmentedControlContext = React.createContext<
24
+ TypeSegmentedControlContext | null | undefined
25
+ >(null);
26
+
27
+ const SegmentedControlItem = ({
28
+ value,
29
+ children,
30
+ disabled,
31
+ ...rest
32
+ }: TypeSegmentedControlItemProps) => {
33
+ const context = useContext(SegmentedControlContext);
34
+
35
+ if (!context) {
36
+ return null;
37
+ }
38
+
39
+ const { name, selectedValue, onChange } = context;
40
+
41
+ /* eslint-disable-next-line */
42
+ const [id] = useState(`${name}-${idCounter++}`);
43
+ const isChecked = value === selectedValue;
44
+
45
+ return (
46
+ <SegmentedControlItemContainer
47
+ data-segmentedcontrol-isactive={isChecked}
48
+ data-qa-segmentedcontrol-item={value}
49
+ data-qa-segmentedcontrol-ischecked={isChecked === true}
50
+ disabled={disabled}
51
+ {...rest}
52
+ >
53
+ <input
54
+ type="radio"
55
+ id={id}
56
+ name={name}
57
+ value={value}
58
+ checked={isChecked}
59
+ onChange={onChange}
60
+ disabled={disabled}
61
+ />
62
+ <SegmentedControlLabel as="label" htmlFor={id} isActive={isChecked}>
63
+ {children}
64
+ </SegmentedControlLabel>
65
+ </SegmentedControlItemContainer>
66
+ );
67
+ };
68
+
69
+ const SegmentedControl = ({
70
+ selectedValue,
71
+ label,
72
+ onChange,
73
+ children,
74
+ ...rest
75
+ }: TypeSegmentedControlProps) => {
76
+ const [name] = useState(`Racine-segmented-control-${nameCounter++}`);
77
+ return (
78
+ <SegmentedControlContext.Provider
79
+ value={{
80
+ name,
81
+ selectedValue,
82
+ onChange,
83
+ }}
84
+ >
85
+ <SegmentedControlContainer
86
+ forwardedAs="fieldset"
87
+ data-qa-segmentedcontrol={label}
88
+ data-qa-segmentedcontrol-value={selectedValue}
89
+ {...rest}
90
+ >
91
+ <legend>{label}</legend>
92
+
93
+ <Box display="flex">{children}</Box>
94
+ </SegmentedControlContainer>
95
+ </SegmentedControlContext.Provider>
96
+ );
97
+ };
98
+
99
+ SegmentedControlItem.displayName = "SegmentedControl.Item";
100
+ SegmentedControl.Item = SegmentedControlItem;
101
+
102
+ export default SegmentedControl;
@@ -0,0 +1,25 @@
1
+ import type { TypeContainerProps } from "@sproutsocial/seeds-react-box";
2
+ import * as React from "react";
3
+
4
+ export interface TypeSegmentedControlItemProps {
5
+ /** The value of this item. Should be unique among sibling items. */
6
+ value: string;
7
+ children: React.ReactNode;
8
+ /** Disables user action and applies a disabled style on the component */
9
+ disabled?: boolean;
10
+ }
11
+
12
+ export interface TypeSegmentedControlProps extends TypeContainerProps {
13
+ /** The value of the currently selected item. Should match the value prop of one of the child items */
14
+ selectedValue: string;
15
+
16
+ /** The title of the segmented control, used for accessibility purposes */
17
+ label: string;
18
+
19
+ /** Called when the user selects a new item. You can access the value of the newly selected item using "event.target.value" */
20
+ onChange: (e: React.SyntheticEvent<HTMLInputElement>) => void;
21
+
22
+ /** Disables user action and applies a disabled style on the component */
23
+ disabled?: boolean;
24
+ children: React.ReactNode;
25
+ }
@@ -0,0 +1,84 @@
1
+ import React, { useState } from "react";
2
+ import {
3
+ render,
4
+ fireEvent,
5
+ screen,
6
+ } from "@sproutsocial/seeds-react-testing-library";
7
+ import SegmentedControl from "../SegmentedControl";
8
+
9
+ // @ts-ignore IDK what props should be
10
+ const StatefulSegmentedControl = (props) => {
11
+ const [value, setValue] = useState("1");
12
+
13
+ return (
14
+ <SegmentedControl
15
+ selectedValue={value}
16
+ label="Segmented control component"
17
+ onChange={(e) => setValue((e.target as HTMLInputElement).value)}
18
+ {...props}
19
+ >
20
+ <SegmentedControl.Item value="1">Test 1</SegmentedControl.Item>
21
+ <SegmentedControl.Item value="2">Test 2</SegmentedControl.Item>
22
+ <SegmentedControl.Item value="3">Test 3</SegmentedControl.Item>
23
+ </SegmentedControl>
24
+ );
25
+ };
26
+
27
+ describe("SegmentedControl", () => {
28
+ it("should select the correct item when clicked", async () => {
29
+ render(<StatefulSegmentedControl />);
30
+
31
+ const item1 = screen.getByText("Test 1");
32
+ const item2 = screen.getByText("Test 2");
33
+ const item3 = screen.getByText("Test 3");
34
+
35
+ expect(item1?.parentNode).toHaveAttribute(
36
+ "data-segmentedcontrol-isactive",
37
+ "true"
38
+ );
39
+
40
+ expect(item2?.parentNode).toHaveAttribute(
41
+ "data-segmentedcontrol-isactive",
42
+ "false"
43
+ );
44
+
45
+ expect(item3?.parentNode).toHaveAttribute(
46
+ "data-segmentedcontrol-isactive",
47
+ "false"
48
+ );
49
+
50
+ fireEvent.click(item3);
51
+
52
+ expect(item1?.parentNode).toHaveAttribute(
53
+ "data-segmentedcontrol-isactive",
54
+ "false"
55
+ );
56
+
57
+ expect(item2?.parentNode).toHaveAttribute(
58
+ "data-segmentedcontrol-isactive",
59
+ "false"
60
+ );
61
+
62
+ expect(item3?.parentNode).toHaveAttribute(
63
+ "data-segmentedcontrol-isactive",
64
+ "true"
65
+ );
66
+
67
+ fireEvent.click(item2);
68
+
69
+ expect(item1?.parentNode).toHaveAttribute(
70
+ "data-segmentedcontrol-isactive",
71
+ "false"
72
+ );
73
+
74
+ expect(item2?.parentNode).toHaveAttribute(
75
+ "data-segmentedcontrol-isactive",
76
+ "true"
77
+ );
78
+
79
+ expect(item3?.parentNode).toHaveAttribute(
80
+ "data-segmentedcontrol-isactive",
81
+ "false"
82
+ );
83
+ });
84
+ });
@@ -0,0 +1,24 @@
1
+ import * as React from "react";
2
+ import SegmentedControl from "../SegmentedControl";
3
+
4
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
5
+ function SegmentedControlTypes() {
6
+ const mockChange = jest.fn();
7
+ return (
8
+ <>
9
+ <SegmentedControl
10
+ selectedValue="orange"
11
+ label="Fruits"
12
+ onChange={mockChange}
13
+ height="34px"
14
+ ml="300"
15
+ >
16
+ <SegmentedControl.Item value="apple">Apple</SegmentedControl.Item>
17
+ <SegmentedControl.Item value="orange">Orange</SegmentedControl.Item>
18
+ <SegmentedControl.Item value="banana">Banana</SegmentedControl.Item>
19
+ </SegmentedControl>
20
+ {/* @ts-expect-error - test that invalid and missing props are rejected */}
21
+ <SegmentedControl />
22
+ </>
23
+ );
24
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ import SegmentedControl from "./SegmentedControl";
2
+
3
+ export default SegmentedControl;
4
+ export { SegmentedControl };
5
+ export * from "./SegmentedControlTypes";
@@ -0,0 +1,7 @@
1
+ import "styled-components";
2
+ import { TypeTheme } from "@sproutsocial/seeds-react-theme";
3
+
4
+ declare module "styled-components" {
5
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
6
+ export interface DefaultTheme extends TypeTheme {}
7
+ }
package/src/styles.ts ADDED
@@ -0,0 +1,93 @@
1
+ import styled, { css } from "styled-components";
2
+ import {
3
+ visuallyHidden,
4
+ focusRing,
5
+ disabled,
6
+ } from "@sproutsocial/seeds-react-mixins";
7
+ import Box from "@sproutsocial/seeds-react-box";
8
+ import Text from "@sproutsocial/seeds-react-text";
9
+ import type {
10
+ TypeSegmentedControlProps,
11
+ TypeSegmentedControlItemProps,
12
+ } from "./SegmentedControlTypes";
13
+
14
+ export const SegmentedControlContainer = styled(Box)<
15
+ Pick<TypeSegmentedControlProps, "disabled">
16
+ >`
17
+ border: 1px solid
18
+ ${(props) => props.theme.colors.button.secondary.border.base};
19
+ border-radius: ${(props) => props.theme.radii.outer};
20
+ padding: ${(props) => props.theme.space[100]};
21
+
22
+ legend {
23
+ ${visuallyHidden}
24
+ }
25
+
26
+ ${(props) => props.disabled && disabled}
27
+ `;
28
+
29
+ export const SegmentedControlItemContainer = styled(Box)<
30
+ Pick<TypeSegmentedControlItemProps, "disabled">
31
+ >`
32
+ flex: 1 1 auto;
33
+ display: flex;
34
+ cursor: pointer;
35
+
36
+ & + & {
37
+ margin-left: ${(props) => props.theme.space[100]};
38
+ }
39
+
40
+ &:focus-within label {
41
+ ${focusRing}
42
+ }
43
+
44
+ input {
45
+ ${visuallyHidden}
46
+ }
47
+
48
+ ${(props) => props.disabled && disabled}
49
+ `;
50
+
51
+ interface TypeSegmentedControlState {
52
+ isActive: boolean;
53
+ }
54
+
55
+ export const SegmentedControlLabel = styled(Text)<TypeSegmentedControlState>`
56
+ flex: 1 1 auto;
57
+ display: flex;
58
+ align-items: center;
59
+ justify-content: center;
60
+ text-align: center;
61
+ color: ${(props) => props.theme.colors.text.body};
62
+ cursor: pointer;
63
+ font-size: ${(props) => props.theme.typography[200].fontSize};
64
+ /**
65
+ * Matches default line height of Icon. Also matches the overall height of
66
+ * Input, Select, and Button.
67
+ */
68
+ line-height: 16px;
69
+ font-weight: ${(props) => props.theme.fontWeights.semibold};
70
+
71
+ border-radius: ${(props) => props.theme.radii.inner};
72
+ /* To match the height of buttons... 350 padding - 2px top and bottom padding of the parent - 1px border on top and bottom */
73
+ padding: calc(${(props) => props.theme.space[350]} - 6px);
74
+ transition: all ${(props) => props.theme.duration.fast};
75
+
76
+ &:hover {
77
+ background-color: ${(props) =>
78
+ props.theme.colors.listItem.background.hover};
79
+ }
80
+
81
+ ${(props) =>
82
+ props.isActive &&
83
+ css`
84
+ color: ${(props) => props.theme.colors.text.inverse};
85
+ background-color: ${(props) =>
86
+ props.theme.colors.listItem.background.selected};
87
+
88
+ &:hover {
89
+ background-color: ${(props) =>
90
+ props.theme.colors.listItem.background.selected};
91
+ }
92
+ `}
93
+ `;
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "@sproutsocial/seeds-tsconfig/bundler/dom/library-monorepo",
3
+ "compilerOptions": {
4
+ "jsx": "react-jsx",
5
+ "module": "esnext"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["node_modules", "dist", "coverage", "**/*.stories.tsx"]
9
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig((options) => ({
4
+ entry: ["src/index.ts"],
5
+ format: ["cjs", "esm"],
6
+ clean: true,
7
+ legacyOutput: true,
8
+ dts: options.dts,
9
+ external: ["react"],
10
+ sourcemap: true,
11
+ metafile: options.metafile,
12
+ }));