@transferwise/components 0.0.0-experimental-d585e0b → 0.0.0-experimental-44a3fcb

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/build/main.css CHANGED
@@ -4640,6 +4640,104 @@ html:not([dir="rtl"]) .np-navigation-option {
4640
4640
  .np-theme-personal .np-dropdown-menu .np-dropdown-item--focused {
4641
4641
  box-shadow: inset 0 0 0 2px var(--color-interactive-primary);
4642
4642
  }
4643
+ .segmented-control {
4644
+ box-sizing: border-box;
4645
+ --segment-highlight-width: 0;
4646
+ --segment-highlight-x: var(--size-4);
4647
+ }
4648
+ .segmented-control__segments {
4649
+ display: inline-flex;
4650
+ position: relative;
4651
+ justify-content: center;
4652
+ align-items: center;
4653
+ background: rgba(134,167,189,0.10196);
4654
+ background: var(--color-background-neutral);
4655
+ padding: 4px;
4656
+ padding: var(--size-4);
4657
+ border-radius: 24px;
4658
+ border-radius: var(--size-24);
4659
+ width: 100%;
4660
+ transition: outline 300ms;
4661
+ outline: 2px solid transparent;
4662
+ }
4663
+ .segmented-control--input:has(:focus-visible) > .segmented-control__segments::after {
4664
+ outline: 2px solid var(--color-interactive-primary);
4665
+ }
4666
+ .segmented-control__segments::after {
4667
+ content: "";
4668
+ background: #ffffff;
4669
+ background: var(--color-background-screen);
4670
+ width: var(--segment-highlight-width);
4671
+ position: absolute;
4672
+ top: 4px;
4673
+ top: var(--size-4);
4674
+ bottom: 4px;
4675
+ bottom: var(--size-4);
4676
+ left: var(--segment-highlight-x);
4677
+ z-index: 0;
4678
+ border-radius: 24px;
4679
+ border-radius: var(--size-24);
4680
+ transition: left 300ms;
4681
+ }
4682
+ .segmented-control__segments--no-animate::after {
4683
+ transition: none !important;
4684
+ }
4685
+ .segmented-control__segment {
4686
+ position: relative;
4687
+ flex: 1 1 100%;
4688
+ align-items: center;
4689
+ text-align: center;
4690
+ vertical-align: middle;
4691
+ min-height: 40px;
4692
+ min-height: var(--size-40);
4693
+ line-height: 40px;
4694
+ line-height: var(--size-40);
4695
+ margin: 0;
4696
+ border-radius: 24px;
4697
+ border-radius: var(--size-24);
4698
+ z-index: 1;
4699
+ cursor: pointer;
4700
+ transition: font-weight 300ms, background 300ms;
4701
+ }
4702
+ .segmented-control__segment:hover {
4703
+ background: rgba(0,0,0,0.10196);
4704
+ background: var(--color-background-overlay);
4705
+ }
4706
+ .segmented-control__radio-input {
4707
+ position: fixed;
4708
+ opacity: 0;
4709
+ pointer-events: none;
4710
+ }
4711
+ .segmented-control__button {
4712
+ background: none;
4713
+ -webkit-appearance: none;
4714
+ -moz-appearance: none;
4715
+ appearance: none;
4716
+ border: none;
4717
+ outline: none;
4718
+ width: 100%;
4719
+ height: 100%;
4720
+ font: inherit;
4721
+ padding: none;
4722
+ outline: 2px solid transparent;
4723
+ }
4724
+ .segmented-control__button:focus {
4725
+ outline-offset: 0px;
4726
+ }
4727
+ .segmented-control__button:focus-visible {
4728
+ outline-color: var(--color-interactive-primary);
4729
+ }
4730
+ .segmented-control__selected-segment:hover {
4731
+ background: transparent;
4732
+ }
4733
+ .segmented-control__selected-segment {
4734
+ font-weight: 800;
4735
+ font-weight: var(--font-weight-bold);
4736
+ }
4737
+ .segmented-control__label-text {
4738
+ white-space: nowrap;
4739
+ color: var(--color-interactive-primary);
4740
+ }
4643
4741
  .np-summary {
4644
4742
  min-width: 280px;
4645
4743
  }
@@ -4640,6 +4640,104 @@ html:not([dir="rtl"]) .np-navigation-option {
4640
4640
  .np-theme-personal .np-dropdown-menu .np-dropdown-item--focused {
4641
4641
  box-shadow: inset 0 0 0 2px var(--color-interactive-primary);
4642
4642
  }
4643
+ .segmented-control {
4644
+ box-sizing: border-box;
4645
+ --segment-highlight-width: 0;
4646
+ --segment-highlight-x: var(--size-4);
4647
+ }
4648
+ .segmented-control__segments {
4649
+ display: inline-flex;
4650
+ position: relative;
4651
+ justify-content: center;
4652
+ align-items: center;
4653
+ background: rgba(134,167,189,0.10196);
4654
+ background: var(--color-background-neutral);
4655
+ padding: 4px;
4656
+ padding: var(--size-4);
4657
+ border-radius: 24px;
4658
+ border-radius: var(--size-24);
4659
+ width: 100%;
4660
+ transition: outline 300ms;
4661
+ outline: 2px solid transparent;
4662
+ }
4663
+ .segmented-control--input:has(:focus-visible) > .segmented-control__segments::after {
4664
+ outline: 2px solid var(--color-interactive-primary);
4665
+ }
4666
+ .segmented-control__segments::after {
4667
+ content: "";
4668
+ background: #ffffff;
4669
+ background: var(--color-background-screen);
4670
+ width: var(--segment-highlight-width);
4671
+ position: absolute;
4672
+ top: 4px;
4673
+ top: var(--size-4);
4674
+ bottom: 4px;
4675
+ bottom: var(--size-4);
4676
+ left: var(--segment-highlight-x);
4677
+ z-index: 0;
4678
+ border-radius: 24px;
4679
+ border-radius: var(--size-24);
4680
+ transition: left 300ms;
4681
+ }
4682
+ .segmented-control__segments--no-animate::after {
4683
+ transition: none !important;
4684
+ }
4685
+ .segmented-control__segment {
4686
+ position: relative;
4687
+ flex: 1 1 100%;
4688
+ align-items: center;
4689
+ text-align: center;
4690
+ vertical-align: middle;
4691
+ min-height: 40px;
4692
+ min-height: var(--size-40);
4693
+ line-height: 40px;
4694
+ line-height: var(--size-40);
4695
+ margin: 0;
4696
+ border-radius: 24px;
4697
+ border-radius: var(--size-24);
4698
+ z-index: 1;
4699
+ cursor: pointer;
4700
+ transition: font-weight 300ms, background 300ms;
4701
+ }
4702
+ .segmented-control__segment:hover {
4703
+ background: rgba(0,0,0,0.10196);
4704
+ background: var(--color-background-overlay);
4705
+ }
4706
+ .segmented-control__radio-input {
4707
+ position: fixed;
4708
+ opacity: 0;
4709
+ pointer-events: none;
4710
+ }
4711
+ .segmented-control__button {
4712
+ background: none;
4713
+ -webkit-appearance: none;
4714
+ -moz-appearance: none;
4715
+ appearance: none;
4716
+ border: none;
4717
+ outline: none;
4718
+ width: 100%;
4719
+ height: 100%;
4720
+ font: inherit;
4721
+ padding: none;
4722
+ outline: 2px solid transparent;
4723
+ }
4724
+ .segmented-control__button:focus {
4725
+ outline-offset: 0px;
4726
+ }
4727
+ .segmented-control__button:focus-visible {
4728
+ outline-color: var(--color-interactive-primary);
4729
+ }
4730
+ .segmented-control__selected-segment:hover {
4731
+ background: transparent;
4732
+ }
4733
+ .segmented-control__selected-segment {
4734
+ font-weight: 800;
4735
+ font-weight: var(--font-weight-bold);
4736
+ }
4737
+ .segmented-control__label-text {
4738
+ white-space: nowrap;
4739
+ color: var(--color-interactive-primary);
4740
+ }
4643
4741
  .np-summary {
4644
4742
  min-width: 280px;
4645
4743
  }
@@ -0,0 +1,98 @@
1
+ .segmented-control {
2
+ box-sizing: border-box;
3
+ --segment-highlight-width: 0;
4
+ --segment-highlight-x: var(--size-4);
5
+ }
6
+ .segmented-control__segments {
7
+ display: inline-flex;
8
+ position: relative;
9
+ justify-content: center;
10
+ align-items: center;
11
+ background: rgba(134,167,189,0.10196);
12
+ background: var(--color-background-neutral);
13
+ padding: 4px;
14
+ padding: var(--size-4);
15
+ border-radius: 24px;
16
+ border-radius: var(--size-24);
17
+ width: 100%;
18
+ transition: outline 300ms;
19
+ outline: 2px solid transparent;
20
+ }
21
+ .segmented-control--input:has(:focus-visible) > .segmented-control__segments::after {
22
+ outline: 2px solid var(--color-interactive-primary);
23
+ }
24
+ .segmented-control__segments::after {
25
+ content: "";
26
+ background: #ffffff;
27
+ background: var(--color-background-screen);
28
+ width: var(--segment-highlight-width);
29
+ position: absolute;
30
+ top: 4px;
31
+ top: var(--size-4);
32
+ bottom: 4px;
33
+ bottom: var(--size-4);
34
+ left: var(--segment-highlight-x);
35
+ z-index: 0;
36
+ border-radius: 24px;
37
+ border-radius: var(--size-24);
38
+ transition: left 300ms;
39
+ }
40
+ .segmented-control__segments--no-animate::after {
41
+ transition: none !important;
42
+ }
43
+ .segmented-control__segment {
44
+ position: relative;
45
+ flex: 1 1 100%;
46
+ align-items: center;
47
+ text-align: center;
48
+ vertical-align: middle;
49
+ min-height: 40px;
50
+ min-height: var(--size-40);
51
+ line-height: 40px;
52
+ line-height: var(--size-40);
53
+ margin: 0;
54
+ border-radius: 24px;
55
+ border-radius: var(--size-24);
56
+ z-index: 1;
57
+ cursor: pointer;
58
+ transition: font-weight 300ms, background 300ms;
59
+ }
60
+ .segmented-control__segment:hover {
61
+ background: rgba(0,0,0,0.10196);
62
+ background: var(--color-background-overlay);
63
+ }
64
+ .segmented-control__radio-input {
65
+ position: fixed;
66
+ opacity: 0;
67
+ pointer-events: none;
68
+ }
69
+ .segmented-control__button {
70
+ background: none;
71
+ -webkit-appearance: none;
72
+ -moz-appearance: none;
73
+ appearance: none;
74
+ border: none;
75
+ outline: none;
76
+ width: 100%;
77
+ height: 100%;
78
+ font: inherit;
79
+ padding: none;
80
+ outline: 2px solid transparent;
81
+ }
82
+ .segmented-control__button:focus {
83
+ outline-offset: 0px;
84
+ }
85
+ .segmented-control__button:focus-visible {
86
+ outline-color: var(--color-interactive-primary);
87
+ }
88
+ .segmented-control__selected-segment:hover {
89
+ background: transparent;
90
+ }
91
+ .segmented-control__selected-segment {
92
+ font-weight: 800;
93
+ font-weight: var(--font-weight-bold);
94
+ }
95
+ .segmented-control__label-text {
96
+ white-space: nowrap;
97
+ color: var(--color-interactive-primary);
98
+ }
@@ -0,0 +1,23 @@
1
+ /// <reference types="react" />
2
+ type SegmentedControlPropsBase = {
3
+ name: string;
4
+ defaultValue: string;
5
+ type: 'input' | 'navigation';
6
+ segments: {
7
+ label: string;
8
+ value: string;
9
+ }[];
10
+ onChange: (value: string) => void;
11
+ };
12
+ type SegmentedControlNavigationProps = {
13
+ type: 'navigation';
14
+ controls: string;
15
+ };
16
+ type SegmentedControlInputProps = {
17
+ type: 'input';
18
+ controls?: never;
19
+ };
20
+ export type SegmentedControlProps = SegmentedControlPropsBase & (SegmentedControlNavigationProps | SegmentedControlInputProps);
21
+ export declare const SegmentedControl: ({ name, defaultValue, type, segments, controls, onChange, }: SegmentedControlProps) => import("react").JSX.Element;
22
+ export {};
23
+ //# sourceMappingURL=SegmentedControl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SegmentedControl.d.ts","sourceRoot":"","sources":["../../../src/segmentedControl/SegmentedControl.tsx"],"names":[],"mappings":";AAGA,KAAK,yBAAyB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,OAAO,GAAG,YAAY,CAAC;IAC7B,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7C,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC,CAAC;AAEF,KAAK,+BAA+B,GAAG;IACrC,IAAI,EAAE,YAAY,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,KAAK,0BAA0B,GAAG;IAChC,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,yBAAyB,GAC3D,CAAC,+BAA+B,GAAG,0BAA0B,CAAC,CAAC;AAEjE,eAAO,MAAM,gBAAgB,gEAO1B,qBAAqB,gCA0GvB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@transferwise/components",
3
- "version": "0.0.0-experimental-d585e0b",
3
+ "version": "0.0.0-experimental-44a3fcb",
4
4
  "description": "Neptune React components",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -78,8 +78,8 @@
78
78
  "rollup": "^3.28.1",
79
79
  "storybook": "^7.4.5",
80
80
  "@transferwise/less-config": "3.1.0",
81
- "@transferwise/neptune-css": "14.9.3",
82
- "@wise/components-theming": "0.8.4"
81
+ "@wise/components-theming": "0.8.4",
82
+ "@transferwise/neptune-css": "14.9.3"
83
83
  },
84
84
  "peerDependencies": {
85
85
  "@transferwise/icons": "^3.7.0",
package/src/main.css CHANGED
@@ -4640,6 +4640,104 @@ html:not([dir="rtl"]) .np-navigation-option {
4640
4640
  .np-theme-personal .np-dropdown-menu .np-dropdown-item--focused {
4641
4641
  box-shadow: inset 0 0 0 2px var(--color-interactive-primary);
4642
4642
  }
4643
+ .segmented-control {
4644
+ box-sizing: border-box;
4645
+ --segment-highlight-width: 0;
4646
+ --segment-highlight-x: var(--size-4);
4647
+ }
4648
+ .segmented-control__segments {
4649
+ display: inline-flex;
4650
+ position: relative;
4651
+ justify-content: center;
4652
+ align-items: center;
4653
+ background: rgba(134,167,189,0.10196);
4654
+ background: var(--color-background-neutral);
4655
+ padding: 4px;
4656
+ padding: var(--size-4);
4657
+ border-radius: 24px;
4658
+ border-radius: var(--size-24);
4659
+ width: 100%;
4660
+ transition: outline 300ms;
4661
+ outline: 2px solid transparent;
4662
+ }
4663
+ .segmented-control--input:has(:focus-visible) > .segmented-control__segments::after {
4664
+ outline: 2px solid var(--color-interactive-primary);
4665
+ }
4666
+ .segmented-control__segments::after {
4667
+ content: "";
4668
+ background: #ffffff;
4669
+ background: var(--color-background-screen);
4670
+ width: var(--segment-highlight-width);
4671
+ position: absolute;
4672
+ top: 4px;
4673
+ top: var(--size-4);
4674
+ bottom: 4px;
4675
+ bottom: var(--size-4);
4676
+ left: var(--segment-highlight-x);
4677
+ z-index: 0;
4678
+ border-radius: 24px;
4679
+ border-radius: var(--size-24);
4680
+ transition: left 300ms;
4681
+ }
4682
+ .segmented-control__segments--no-animate::after {
4683
+ transition: none !important;
4684
+ }
4685
+ .segmented-control__segment {
4686
+ position: relative;
4687
+ flex: 1 1 100%;
4688
+ align-items: center;
4689
+ text-align: center;
4690
+ vertical-align: middle;
4691
+ min-height: 40px;
4692
+ min-height: var(--size-40);
4693
+ line-height: 40px;
4694
+ line-height: var(--size-40);
4695
+ margin: 0;
4696
+ border-radius: 24px;
4697
+ border-radius: var(--size-24);
4698
+ z-index: 1;
4699
+ cursor: pointer;
4700
+ transition: font-weight 300ms, background 300ms;
4701
+ }
4702
+ .segmented-control__segment:hover {
4703
+ background: rgba(0,0,0,0.10196);
4704
+ background: var(--color-background-overlay);
4705
+ }
4706
+ .segmented-control__radio-input {
4707
+ position: fixed;
4708
+ opacity: 0;
4709
+ pointer-events: none;
4710
+ }
4711
+ .segmented-control__button {
4712
+ background: none;
4713
+ -webkit-appearance: none;
4714
+ -moz-appearance: none;
4715
+ appearance: none;
4716
+ border: none;
4717
+ outline: none;
4718
+ width: 100%;
4719
+ height: 100%;
4720
+ font: inherit;
4721
+ padding: none;
4722
+ outline: 2px solid transparent;
4723
+ }
4724
+ .segmented-control__button:focus {
4725
+ outline-offset: 0px;
4726
+ }
4727
+ .segmented-control__button:focus-visible {
4728
+ outline-color: var(--color-interactive-primary);
4729
+ }
4730
+ .segmented-control__selected-segment:hover {
4731
+ background: transparent;
4732
+ }
4733
+ .segmented-control__selected-segment {
4734
+ font-weight: 800;
4735
+ font-weight: var(--font-weight-bold);
4736
+ }
4737
+ .segmented-control__label-text {
4738
+ white-space: nowrap;
4739
+ color: var(--color-interactive-primary);
4740
+ }
4643
4741
  .np-summary {
4644
4742
  min-width: 280px;
4645
4743
  }
package/src/main.less CHANGED
@@ -53,6 +53,7 @@
53
53
  @import "./statusIcon/StatusIcon.less";
54
54
  @import "./stepper/Stepper.less";
55
55
  @import "./select/Select.less";
56
+ @import "./segmentedControl/SegmentedControl.less";
56
57
  @import "./summary/Summary.less";
57
58
  @import "./switch/Switch.less";
58
59
  @import "./tabs/Tabs.less";
@@ -0,0 +1,98 @@
1
+ .segmented-control {
2
+ box-sizing: border-box;
3
+ --segment-highlight-width: 0;
4
+ --segment-highlight-x: var(--size-4);
5
+ }
6
+ .segmented-control__segments {
7
+ display: inline-flex;
8
+ position: relative;
9
+ justify-content: center;
10
+ align-items: center;
11
+ background: rgba(134,167,189,0.10196);
12
+ background: var(--color-background-neutral);
13
+ padding: 4px;
14
+ padding: var(--size-4);
15
+ border-radius: 24px;
16
+ border-radius: var(--size-24);
17
+ width: 100%;
18
+ transition: outline 300ms;
19
+ outline: 2px solid transparent;
20
+ }
21
+ .segmented-control--input:has(:focus-visible) > .segmented-control__segments::after {
22
+ outline: 2px solid var(--color-interactive-primary);
23
+ }
24
+ .segmented-control__segments::after {
25
+ content: "";
26
+ background: #ffffff;
27
+ background: var(--color-background-screen);
28
+ width: var(--segment-highlight-width);
29
+ position: absolute;
30
+ top: 4px;
31
+ top: var(--size-4);
32
+ bottom: 4px;
33
+ bottom: var(--size-4);
34
+ left: var(--segment-highlight-x);
35
+ z-index: 0;
36
+ border-radius: 24px;
37
+ border-radius: var(--size-24);
38
+ transition: left 300ms;
39
+ }
40
+ .segmented-control__segments--no-animate::after {
41
+ transition: none !important;
42
+ }
43
+ .segmented-control__segment {
44
+ position: relative;
45
+ flex: 1 1 100%;
46
+ align-items: center;
47
+ text-align: center;
48
+ vertical-align: middle;
49
+ min-height: 40px;
50
+ min-height: var(--size-40);
51
+ line-height: 40px;
52
+ line-height: var(--size-40);
53
+ margin: 0;
54
+ border-radius: 24px;
55
+ border-radius: var(--size-24);
56
+ z-index: 1;
57
+ cursor: pointer;
58
+ transition: font-weight 300ms, background 300ms;
59
+ }
60
+ .segmented-control__segment:hover {
61
+ background: rgba(0,0,0,0.10196);
62
+ background: var(--color-background-overlay);
63
+ }
64
+ .segmented-control__radio-input {
65
+ position: fixed;
66
+ opacity: 0;
67
+ pointer-events: none;
68
+ }
69
+ .segmented-control__button {
70
+ background: none;
71
+ -webkit-appearance: none;
72
+ -moz-appearance: none;
73
+ appearance: none;
74
+ border: none;
75
+ outline: none;
76
+ width: 100%;
77
+ height: 100%;
78
+ font: inherit;
79
+ padding: none;
80
+ outline: 2px solid transparent;
81
+ }
82
+ .segmented-control__button:focus {
83
+ outline-offset: 0px;
84
+ }
85
+ .segmented-control__button:focus-visible {
86
+ outline-color: var(--color-interactive-primary);
87
+ }
88
+ .segmented-control__selected-segment:hover {
89
+ background: transparent;
90
+ }
91
+ .segmented-control__selected-segment {
92
+ font-weight: 800;
93
+ font-weight: var(--font-weight-bold);
94
+ }
95
+ .segmented-control__label-text {
96
+ white-space: nowrap;
97
+ color: var(--color-interactive-primary);
98
+ }
@@ -0,0 +1,97 @@
1
+ .segmented-control {
2
+ box-sizing: border-box;
3
+ --segment-highlight-width: 0;
4
+ --segment-highlight-x: var(--size-4);
5
+ }
6
+
7
+ .segmented-control__segments {
8
+ display: inline-flex;
9
+ position: relative;
10
+ justify-content: center;
11
+ align-items: center;
12
+ background: var(--color-background-neutral);
13
+ padding: var(--size-4);
14
+ border-radius: var(--size-24);
15
+ width: 100%;
16
+ transition: outline 300ms;
17
+ outline: 2px solid transparent;
18
+ }
19
+
20
+ .segmented-control--input:has(:focus-visible) > .segmented-control__segments::after {
21
+ outline: 2px solid var(--color-interactive-primary);
22
+ }
23
+
24
+ .segmented-control__segments::after {
25
+ content: "";
26
+ background: var(--color-background-screen);
27
+ width: var(--segment-highlight-width);
28
+ position: absolute;
29
+ top: var(--size-4);
30
+ bottom: var(--size-4);
31
+ left: var(--segment-highlight-x);
32
+ z-index: 0;
33
+ border-radius: var(--size-24);
34
+ transition: left 300ms;
35
+ }
36
+
37
+ .segmented-control__segments--no-animate::after {
38
+ transition: none !important;
39
+ }
40
+
41
+ .segmented-control__segment {
42
+ position: relative;
43
+ flex: 1 1 100%;
44
+ align-items: center;
45
+ text-align: center;
46
+ vertical-align: middle;
47
+ min-height: var(--size-40);
48
+ line-height: var(--size-40);
49
+ margin: 0;
50
+ border-radius: var(--size-24);
51
+ z-index: 1;
52
+ cursor: pointer;
53
+ transition: font-weight 300ms, background 300ms;
54
+ }
55
+
56
+ .segmented-control__segment:hover {
57
+ background: var(--color-background-overlay);
58
+ }
59
+
60
+ .segmented-control__radio-input {
61
+ position: fixed;
62
+ opacity: 0;
63
+ pointer-events: none;
64
+ }
65
+
66
+ .segmented-control__button {
67
+ background: none;
68
+ appearance: none;
69
+ border: none;
70
+ outline: none;
71
+ width: 100%;
72
+ height: 100%;
73
+ font: inherit;
74
+ padding: none;
75
+ outline: 2px solid transparent;
76
+ }
77
+
78
+ .segmented-control__button:focus {
79
+ outline-offset: 0px;
80
+ }
81
+
82
+ .segmented-control__button:focus-visible {
83
+ outline-color: var(--color-interactive-primary);
84
+ }
85
+
86
+ .segmented-control__selected-segment:hover {
87
+ background: transparent;
88
+ }
89
+
90
+ .segmented-control__selected-segment {
91
+ font-weight: var(--font-weight-bold);
92
+ }
93
+
94
+ .segmented-control__label-text {
95
+ white-space: nowrap;
96
+ color: var(--color-interactive-primary);
97
+ }
@@ -0,0 +1,50 @@
1
+ import { StoryFn } from '@storybook/react';
2
+ import React from 'react';
3
+
4
+ import { SegmentedControl } from './SegmentedControl';
5
+
6
+ export default {
7
+ component: SegmentedControl,
8
+ title: 'Forms/SegmentedControl',
9
+ };
10
+
11
+ const segments = [
12
+ { label: 'Cupcakes', value: 'cupcakes' },
13
+ { label: 'Sponge cake', value: 'spongecake' },
14
+ { label: 'Carrot cake', value: 'carrotcake' },
15
+ ];
16
+
17
+ const Template: StoryFn = (args) => {
18
+ const [selectedValue, setSelectedValue] = React.useState(segments[0].value);
19
+
20
+ return (
21
+ <div className="p-a-2">
22
+ <SegmentedControl
23
+ name="aRadioGroupName"
24
+ segments={segments}
25
+ {...(args.type === 'navigation'
26
+ ? { type: 'navigation', controls: 'aControlId' }
27
+ : { type: 'input' })}
28
+ defaultValue={selectedValue}
29
+ onChange={setSelectedValue}
30
+ />
31
+ <div className="m-a-2" id="aControlId">
32
+ <p>Selected value: {selectedValue}</p>
33
+ </div>
34
+ </div>
35
+ );
36
+ };
37
+
38
+ export const SegmentedControlDefault = {
39
+ render: Template,
40
+ args: {
41
+ type: 'input',
42
+ },
43
+ };
44
+
45
+ export const SegmentedControlNav = {
46
+ render: Template,
47
+ args: {
48
+ type: 'navigation',
49
+ },
50
+ };
@@ -0,0 +1,138 @@
1
+ import classNames from 'classnames';
2
+ import { createRef, useEffect, useRef, useState } from 'react';
3
+
4
+ type SegmentedControlPropsBase = {
5
+ name: string;
6
+ defaultValue: string;
7
+ type: 'input' | 'navigation';
8
+ segments: { label: string; value: string }[];
9
+ onChange: (value: string) => void;
10
+ };
11
+
12
+ type SegmentedControlNavigationProps = {
13
+ type: 'navigation';
14
+ controls: string;
15
+ };
16
+
17
+ type SegmentedControlInputProps = {
18
+ type: 'input';
19
+ controls?: never;
20
+ };
21
+
22
+ export type SegmentedControlProps = SegmentedControlPropsBase &
23
+ (SegmentedControlNavigationProps | SegmentedControlInputProps);
24
+
25
+ export const SegmentedControl = ({
26
+ name,
27
+ defaultValue,
28
+ type,
29
+ segments,
30
+ controls,
31
+ onChange,
32
+ }: SegmentedControlProps) => {
33
+ const [selectedValue, setSelectedValue] = useState(defaultValue || segments[0].value);
34
+ const [animate, setAnimate] = useState(false);
35
+
36
+ const segmentsRef = useRef<HTMLDivElement>(null);
37
+
38
+ const segmentsWithRefs = segments.map((segment) => ({
39
+ ...segment,
40
+ ref: createRef<HTMLLabelElement | HTMLButtonElement>(),
41
+ }));
42
+
43
+ const updateSegmentPosition = () => {
44
+ const selectedSegmentRef = segmentsWithRefs.find(
45
+ (segment) => segment.value === selectedValue,
46
+ )?.ref;
47
+
48
+ // We grab the active segments style object from the ref
49
+ // and set the css variables to the selected segments width and x position.
50
+ // This is so we can animate the highlight to the selected segment
51
+ if (selectedSegmentRef?.current && segmentsRef.current) {
52
+ const { style } = segmentsRef.current;
53
+ style.setProperty('--segment-highlight-width', `${selectedSegmentRef.current.offsetWidth}px`);
54
+ style.setProperty('--segment-highlight-x', `${selectedSegmentRef.current.offsetLeft}px`);
55
+ }
56
+ };
57
+
58
+ useEffect(() => {
59
+ updateSegmentPosition();
60
+ // eslint-disable-next-line react-hooks/exhaustive-deps
61
+ }, [segmentsWithRefs, selectedValue]);
62
+
63
+ useEffect(() => {
64
+ const handleWindowSizeChange = () => {
65
+ setAnimate(false);
66
+ updateSegmentPosition();
67
+ };
68
+
69
+ window.addEventListener('resize', handleWindowSizeChange);
70
+ return () => {
71
+ window.removeEventListener('resize', handleWindowSizeChange);
72
+ };
73
+
74
+ // eslint-disable-next-line react-hooks/exhaustive-deps
75
+ }, []);
76
+
77
+ useEffect(() => {
78
+ onChange(selectedValue);
79
+ }, [onChange, selectedValue]);
80
+
81
+ return (
82
+ <div
83
+ ref={segmentsRef}
84
+ className={classNames('segmented-control', {
85
+ 'segmented-control--input': type === 'input',
86
+ })}
87
+ >
88
+ <div
89
+ className={classNames('segmented-control__segments', {
90
+ 'segmented-control__segments--no-animate': !animate,
91
+ })}
92
+ >
93
+ {segmentsWithRefs.map((segment) =>
94
+ type === 'input' ? (
95
+ <label
96
+ ref={segment.ref as React.RefObject<HTMLLabelElement>}
97
+ key={segment.value}
98
+ className={classNames('segmented-control__segment', {
99
+ 'segmented-control__selected-segment': selectedValue === segment.value,
100
+ })}
101
+ htmlFor={segment.value}
102
+ >
103
+ <input
104
+ type="radio"
105
+ className="segmented-control__radio-input"
106
+ id={segment.value}
107
+ name={name}
108
+ value={segment.value}
109
+ checked={selectedValue === segment.value}
110
+ onChange={() => {
111
+ setAnimate(true);
112
+ setSelectedValue(segment.value);
113
+ }}
114
+ />
115
+ <div className="segmented-control__label-text">{segment.label}</div>
116
+ </label>
117
+ ) : (
118
+ <button
119
+ ref={segment.ref as React.RefObject<HTMLButtonElement>}
120
+ key={segment.value}
121
+ type="button"
122
+ aria-controls={controls}
123
+ className={classNames('segmented-control__segment', 'segmented-control__button', {
124
+ 'segmented-control__selected-segment': selectedValue === segment.value,
125
+ })}
126
+ onClick={() => {
127
+ setAnimate(true);
128
+ setSelectedValue(segment.value);
129
+ }}
130
+ >
131
+ {segment.label}
132
+ </button>
133
+ ),
134
+ )}
135
+ </div>
136
+ </div>
137
+ );
138
+ };