paris 0.12.0 → 0.13.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/CHANGELOG.md +14 -0
- package/package.json +3 -1
- package/src/stories/accordion/Accordion.module.scss +66 -1
- package/src/stories/accordion/Accordion.stories.ts +17 -0
- package/src/stories/accordion/Accordion.tsx +30 -8
- package/src/stories/icon/Info.tsx +8 -0
- package/src/stories/icon/index.ts +1 -0
- package/src/stories/informationaltooltip/InformationalTooltip.module.scss +24 -0
- package/src/stories/informationaltooltip/InformationalTooltip.stories.tsx +99 -0
- package/src/stories/informationaltooltip/InformationalTooltip.tsx +124 -0
- package/src/stories/informationaltooltip/index.ts +1 -0
- package/src/stories/select/Select.module.scss +72 -4
- package/src/stories/select/Select.stories.ts +14 -0
- package/src/stories/select/Select.tsx +40 -2
- package/src/stories/theme/themes.ts +3 -0
- package/src/stories/theme/tokens.ts +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# paris
|
|
2
2
|
|
|
3
|
+
## 0.13.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 60c9428: InformationalTooltip: new component, tooltip with a heading and body text
|
|
8
|
+
- 60c9428: Select: added new `segmented` kind and `segmentedHeight` prop
|
|
9
|
+
- 60c9428: Accordion: added new `card` kind and `size` prop
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 60c9428: Icon: new `info` icon
|
|
14
|
+
- 60c9428: Theme: new blue400 and blue500 tokens, with contentLink variable
|
|
15
|
+
- 60c9428: Select: hover color appears above selected color
|
|
16
|
+
|
|
3
17
|
## 0.12.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "paris",
|
|
3
3
|
"author": "Sanil Chawla <sanil@slingshot.fm> (https://sanil.co)",
|
|
4
4
|
"description": "Paris is Slingshot's React design system. It's a collection of reusable components, design tokens, and guidelines that help us build consistent, accessible, and performant user interfaces.",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.13.0",
|
|
6
6
|
"homepage": "https://paris.slingshot.fm",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"./drawer": "./src/stories/drawer/index.ts",
|
|
42
42
|
"./field": "./src/stories/field/index.ts",
|
|
43
43
|
"./icon": "./src/stories/icon/index.ts",
|
|
44
|
+
"./informationaltooltip": "./src/stories/informationaltooltip/index.ts",
|
|
44
45
|
"./input": "./src/stories/input/index.ts",
|
|
45
46
|
"./menu": "./src/stories/menu/index.ts",
|
|
46
47
|
"./pagination": "./src/stories/pagination/index.ts",
|
|
@@ -65,6 +66,7 @@
|
|
|
65
66
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
|
66
67
|
"@headlessui/react": "^1.7.14",
|
|
67
68
|
"@radix-ui/react-checkbox": "^1.0.4",
|
|
69
|
+
"@radix-ui/react-tooltip": "^1.1.8",
|
|
68
70
|
"clsx": "^1.2.1",
|
|
69
71
|
"font-color-contrast": "^11.1.0",
|
|
70
72
|
"framer-motion": "^10.16.4",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.
|
|
1
|
+
.default {
|
|
2
2
|
color: var(--pte-new-colors-contentPrimary);
|
|
3
3
|
border-bottom: 1px solid var(--pte-new-colors-borderMedium);
|
|
4
4
|
cursor: pointer;
|
|
@@ -51,3 +51,68 @@
|
|
|
51
51
|
background-color: var(--pte-new-colors-overlaySubtle);
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
+
|
|
55
|
+
.card {
|
|
56
|
+
color: var(--pte-new-colors-contentPrimary);
|
|
57
|
+
border: 1px solid var(--pte-new-colors-borderStrong);
|
|
58
|
+
border-radius: var(--pte-new-borders-radius-roundedMedium);
|
|
59
|
+
background: var(--pte-new-colors-surfacePrimary);
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
transition: var(--pte-animations-interaction);
|
|
62
|
+
|
|
63
|
+
&.open {
|
|
64
|
+
border-color: var(--pte-new-colors-borderUltrastrong);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.title {
|
|
68
|
+
padding: 10px 12px;
|
|
69
|
+
display: flex;
|
|
70
|
+
justify-content: space-between;
|
|
71
|
+
align-items: center;
|
|
72
|
+
gap: 10px;
|
|
73
|
+
background-color: var(--pte-new-colors-overlayWhiteSubtle);
|
|
74
|
+
|
|
75
|
+
border-bottom: 1px solid transparent;
|
|
76
|
+
transition: var(--pte-animations-interaction);
|
|
77
|
+
|
|
78
|
+
&.large {
|
|
79
|
+
padding: 14px 16px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
&.open {
|
|
83
|
+
background-color: var(--pte-new-colors-overlayMedium);
|
|
84
|
+
border-bottom-color: var(--pte-new-colors-borderStrong);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
&:hover {
|
|
88
|
+
background-color: var(--pte-new-colors-overlayStrong);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.chevron {
|
|
93
|
+
transition: transform var(--pte-animations-duration-gradual) var(--pte-animations-timing-easeInOutExpo);
|
|
94
|
+
transform: rotate(90deg);
|
|
95
|
+
|
|
96
|
+
&.open {
|
|
97
|
+
transform: rotate(-90deg);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.dropdown {
|
|
102
|
+
overflow: hidden;
|
|
103
|
+
cursor: auto;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.dropdownContent {
|
|
107
|
+
padding: 12px;
|
|
108
|
+
display: flex;
|
|
109
|
+
flex-direction: column;
|
|
110
|
+
gap: 12px;
|
|
111
|
+
background-color: var(--pte-new-colors-overlayWhiteSubtle);
|
|
112
|
+
|
|
113
|
+
&.large {
|
|
114
|
+
padding: 16px;
|
|
115
|
+
gap: 16px;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -16,3 +16,20 @@ export const Default: Story = {
|
|
|
16
16
|
children: 'In an alleyway, drinking champagne.',
|
|
17
17
|
},
|
|
18
18
|
};
|
|
19
|
+
|
|
20
|
+
export const Card: Story = {
|
|
21
|
+
args: {
|
|
22
|
+
title: 'Where were we?',
|
|
23
|
+
children: 'In an alleyway, drinking champagne.',
|
|
24
|
+
kind: 'card',
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const CardLarge: Story = {
|
|
29
|
+
args: {
|
|
30
|
+
title: 'Where were we?',
|
|
31
|
+
children: 'In an alleyway, drinking champagne.',
|
|
32
|
+
kind: 'card',
|
|
33
|
+
size: 'large',
|
|
34
|
+
},
|
|
35
|
+
};
|
|
@@ -6,10 +6,21 @@ import { faPlus } from '@fortawesome/free-solid-svg-icons';
|
|
|
6
6
|
import clsx from 'clsx';
|
|
7
7
|
import styles from './Accordion.module.scss';
|
|
8
8
|
import { TextWhenString } from '../utility';
|
|
9
|
+
import { ChevronRight, Icon } from '../icon';
|
|
9
10
|
|
|
10
11
|
export type AccordionProps = {
|
|
11
12
|
/** The title of the Accordion. */
|
|
12
13
|
title?: ReactNode;
|
|
14
|
+
/**
|
|
15
|
+
* The style of the Accordion.
|
|
16
|
+
* @default default
|
|
17
|
+
*/
|
|
18
|
+
kind?: 'default' | 'card';
|
|
19
|
+
/**
|
|
20
|
+
* The size of the Accordion. Only affects kind="card".
|
|
21
|
+
* @default small
|
|
22
|
+
*/
|
|
23
|
+
size?: 'small' | 'large';
|
|
13
24
|
/** Whether the Accordion is open. If provided, the Accordion will be a controlled component. */
|
|
14
25
|
isOpen?: boolean;
|
|
15
26
|
/** A handler for when the Accordion state changes. */
|
|
@@ -32,6 +43,8 @@ export type AccordionProps = {
|
|
|
32
43
|
*/
|
|
33
44
|
export const Accordion: FC<AccordionProps> = ({
|
|
34
45
|
title,
|
|
46
|
+
kind = 'default',
|
|
47
|
+
size = 'small',
|
|
35
48
|
isOpen,
|
|
36
49
|
onOpenChange,
|
|
37
50
|
children,
|
|
@@ -40,10 +53,10 @@ export const Accordion: FC<AccordionProps> = ({
|
|
|
40
53
|
|
|
41
54
|
return (
|
|
42
55
|
<div
|
|
43
|
-
className={styles.
|
|
56
|
+
className={clsx(styles[kind], open && styles.open)}
|
|
44
57
|
>
|
|
45
58
|
<div
|
|
46
|
-
className={clsx(styles.title, open && styles.open)}
|
|
59
|
+
className={clsx(styles.title, styles[size], open && styles.open)}
|
|
47
60
|
onClick={() => setOpen((o) => !o)}
|
|
48
61
|
onKeyDown={(e) => {
|
|
49
62
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
@@ -60,12 +73,21 @@ export const Accordion: FC<AccordionProps> = ({
|
|
|
60
73
|
{title}
|
|
61
74
|
</TextWhenString>
|
|
62
75
|
</div>
|
|
63
|
-
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
{kind === 'default' && (
|
|
77
|
+
<div className={styles.plusIcon}>
|
|
78
|
+
<FontAwesomeIcon
|
|
79
|
+
icon={faPlus}
|
|
80
|
+
className={clsx(open && styles.open)}
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
)}
|
|
84
|
+
{kind === 'card' && (
|
|
85
|
+
<Icon
|
|
86
|
+
icon={ChevronRight}
|
|
87
|
+
size={16}
|
|
88
|
+
className={clsx(styles.chevron, open && styles.open)}
|
|
67
89
|
/>
|
|
68
|
-
|
|
90
|
+
)}
|
|
69
91
|
</div>
|
|
70
92
|
<AnimatePresence>
|
|
71
93
|
{open && (
|
|
@@ -84,7 +106,7 @@ export const Accordion: FC<AccordionProps> = ({
|
|
|
84
106
|
ease: [0.87, 0, 0.13, 1],
|
|
85
107
|
}}
|
|
86
108
|
>
|
|
87
|
-
<div className={styles.dropdownContent}>
|
|
109
|
+
<div className={clsx(styles.dropdownContent, styles[size])}>
|
|
88
110
|
<TextWhenString kind="paragraphXSmall">
|
|
89
111
|
{children}
|
|
90
112
|
</TextWhenString>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { memo } from 'react';
|
|
2
|
+
import type { IconDefinition } from './Icon';
|
|
3
|
+
|
|
4
|
+
export const Info: IconDefinition = memo(({ size }) => (
|
|
5
|
+
<svg width={size} height={size} viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
6
|
+
<path d="M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336c-13.3 0-24 10.7-24 24s10.7 24 24 24h80c13.3 0 24-10.7 24-24s-10.7-24-24-24h-8V248c0-13.3-10.7-24-24-24H216c-13.3 0-24 10.7-24 24s10.7 24 24 24h24v64H216zm40-144a32 32 0 1 0 0-64 32 32 0 1 0 0 64z" fill="currentColor" />
|
|
7
|
+
</svg>
|
|
8
|
+
));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
.tooltip {
|
|
2
|
+
background-color: var(--pte-new-colors-surfacePrimary);
|
|
3
|
+
border-radius: var(--pte-new-borders-radius-rounded);
|
|
4
|
+
box-shadow: var(--pte-new-lighting-deepBelow);
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
justify-content: center;
|
|
8
|
+
gap: 6px;
|
|
9
|
+
width: 280px;
|
|
10
|
+
padding: 12px;
|
|
11
|
+
color: var(--pte-new-colors-contentTertiary);
|
|
12
|
+
|
|
13
|
+
&.medium {
|
|
14
|
+
width: 240px;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.heading {
|
|
19
|
+
color: var(--pte-new-colors-contentSecondary);
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
gap: 8px;
|
|
23
|
+
padding-left: 1px;
|
|
24
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { createElement } from 'react';
|
|
3
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
4
|
+
import { faPlus } from '@fortawesome/free-solid-svg-icons';
|
|
5
|
+
import { InformationalTooltip } from './InformationalTooltip';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof InformationalTooltip> = {
|
|
8
|
+
title: 'Surfaces/InformationalTooltip',
|
|
9
|
+
component: InformationalTooltip,
|
|
10
|
+
tags: ['autodocs'],
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
type Story = StoryObj<typeof InformationalTooltip>;
|
|
15
|
+
|
|
16
|
+
const render: Story['render'] = (args) =>
|
|
17
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
18
|
+
createElement('div', {
|
|
19
|
+
style: { minHeight: '200px' },
|
|
20
|
+
}, createElement(InformationalTooltip, {
|
|
21
|
+
...args,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
export const Default: Story = {
|
|
25
|
+
args: {
|
|
26
|
+
children: 'If you are being payed on 1099s (through transfer outs) you need to pay taxes quarterly. The amount you pay each quarter is a portion of your estimated tax burden for the year, based on your anticipated income amount. If you over/underpay, you will be refunded/owe the difference at the end of the year. ',
|
|
27
|
+
heading: 'Quarterly taxes',
|
|
28
|
+
},
|
|
29
|
+
render,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const Medium: Story = {
|
|
33
|
+
args: {
|
|
34
|
+
children: 'This is a medium tooltip with no heading',
|
|
35
|
+
size: 'medium',
|
|
36
|
+
},
|
|
37
|
+
render,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const CustomTrigger: Story = {
|
|
41
|
+
args: {
|
|
42
|
+
children: 'With some text below',
|
|
43
|
+
heading: 'Another info tooltip',
|
|
44
|
+
size: 'medium',
|
|
45
|
+
trigger: (
|
|
46
|
+
<div>
|
|
47
|
+
This is a custom trigger
|
|
48
|
+
</div>
|
|
49
|
+
),
|
|
50
|
+
},
|
|
51
|
+
render,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const Open: Story = {
|
|
55
|
+
args: {
|
|
56
|
+
children: 'Open boolean set to true',
|
|
57
|
+
size: 'medium',
|
|
58
|
+
open: true,
|
|
59
|
+
},
|
|
60
|
+
render,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const CustomAlign: Story = {
|
|
64
|
+
args: {
|
|
65
|
+
children: 'With some text below',
|
|
66
|
+
heading: 'Another info tooltip',
|
|
67
|
+
size: 'medium',
|
|
68
|
+
align: 'end',
|
|
69
|
+
trigger: (
|
|
70
|
+
<div>
|
|
71
|
+
This tooltip below is set to align = `end`
|
|
72
|
+
</div>
|
|
73
|
+
),
|
|
74
|
+
},
|
|
75
|
+
render,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const HeadingIcon: Story = {
|
|
79
|
+
args: {
|
|
80
|
+
children: 'This is a medium tooltip with a heading',
|
|
81
|
+
size: 'medium',
|
|
82
|
+
headingIcon: (createElement(FontAwesomeIcon, {
|
|
83
|
+
icon: faPlus,
|
|
84
|
+
width: '14px',
|
|
85
|
+
})),
|
|
86
|
+
heading: 'Custom icon',
|
|
87
|
+
},
|
|
88
|
+
render,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const NullIcon: Story = {
|
|
92
|
+
args: {
|
|
93
|
+
children: 'But the headingIcon is null',
|
|
94
|
+
size: 'medium',
|
|
95
|
+
headingIcon: null,
|
|
96
|
+
heading: 'This has a heading',
|
|
97
|
+
},
|
|
98
|
+
render,
|
|
99
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { FC, ReactNode } from 'react';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import * as RadixTooltip from '@radix-ui/react-tooltip';
|
|
4
|
+
import { motion } from 'framer-motion';
|
|
5
|
+
import styles from './InformationalTooltip.module.scss';
|
|
6
|
+
import { TextWhenString } from '../utility';
|
|
7
|
+
import { Icon, Info } from '../icon';
|
|
8
|
+
|
|
9
|
+
export type InformationalTooltipProps = {
|
|
10
|
+
/**
|
|
11
|
+
* The size of the tooltip.
|
|
12
|
+
*
|
|
13
|
+
* @default 'large'
|
|
14
|
+
*/
|
|
15
|
+
size?: 'medium' | 'large';
|
|
16
|
+
/** The element that toggles the tooltip. If none provided, it will default to an info icon */
|
|
17
|
+
trigger?: ReactNode;
|
|
18
|
+
/** The heading text in the tooltip. If null, the heading will be hidden. */
|
|
19
|
+
heading?: string | null;
|
|
20
|
+
/** The heading icon in the tooltip. If undefined, will show info icon. If pass in an element, it will display in the heading. If set to null, will hide icon. */
|
|
21
|
+
headingIcon?: ReactNode | null | undefined;
|
|
22
|
+
/** The contents of the tooltip. */
|
|
23
|
+
children?: ReactNode;
|
|
24
|
+
/**
|
|
25
|
+
* The side of the trigger that will render the tooltip, though is able to move to avoid collisions.
|
|
26
|
+
*
|
|
27
|
+
* @default 'bottom'
|
|
28
|
+
*/
|
|
29
|
+
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
30
|
+
/**
|
|
31
|
+
* Distance in pixels from the trigger
|
|
32
|
+
*
|
|
33
|
+
* @default '6'
|
|
34
|
+
*/
|
|
35
|
+
sideOffset?: number;
|
|
36
|
+
/**
|
|
37
|
+
* The alignment against the trigger, if there are no collisions
|
|
38
|
+
*
|
|
39
|
+
* @default 'start'
|
|
40
|
+
*/
|
|
41
|
+
align?: 'start' | 'center' | 'end';
|
|
42
|
+
/**
|
|
43
|
+
* The tooltip's open state.
|
|
44
|
+
*/
|
|
45
|
+
open?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Event handler called when the open state of the tooltip changes.
|
|
48
|
+
*
|
|
49
|
+
* @param value {boolean} - The new open state of the tooltip.
|
|
50
|
+
*/
|
|
51
|
+
onOpenChange?: (value: boolean) => void | Promise<void>;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* An InformationalTooltip component, based on [`radix-ui/react-tooltip`](https://www.radix-ui.com/primitives/docs/components/tooltip).
|
|
56
|
+
*
|
|
57
|
+
* <hr />
|
|
58
|
+
*
|
|
59
|
+
* To use this component, import it as follows:
|
|
60
|
+
*
|
|
61
|
+
* ```js
|
|
62
|
+
* import { InformationalTooltip } from 'paris/informationaltooltip';
|
|
63
|
+
* ```
|
|
64
|
+
* @constructor
|
|
65
|
+
*/
|
|
66
|
+
export const InformationalTooltip: FC<InformationalTooltipProps> = ({
|
|
67
|
+
size = 'large',
|
|
68
|
+
trigger,
|
|
69
|
+
heading,
|
|
70
|
+
headingIcon,
|
|
71
|
+
children,
|
|
72
|
+
side = 'bottom',
|
|
73
|
+
sideOffset = 6,
|
|
74
|
+
align = 'start',
|
|
75
|
+
open,
|
|
76
|
+
onOpenChange,
|
|
77
|
+
}) => (
|
|
78
|
+
<RadixTooltip.Provider
|
|
79
|
+
delayDuration={150}
|
|
80
|
+
>
|
|
81
|
+
<RadixTooltip.Root
|
|
82
|
+
open={open}
|
|
83
|
+
onOpenChange={onOpenChange}
|
|
84
|
+
>
|
|
85
|
+
<RadixTooltip.Trigger>
|
|
86
|
+
{!trigger ? (
|
|
87
|
+
<Icon icon={Info} size={14} className={styles.icon} />
|
|
88
|
+
) : (
|
|
89
|
+
<>
|
|
90
|
+
{trigger}
|
|
91
|
+
</>
|
|
92
|
+
)}
|
|
93
|
+
</RadixTooltip.Trigger>
|
|
94
|
+
<RadixTooltip.Portal>
|
|
95
|
+
<RadixTooltip.Content
|
|
96
|
+
side={side}
|
|
97
|
+
sideOffset={sideOffset}
|
|
98
|
+
align={align}
|
|
99
|
+
>
|
|
100
|
+
<motion.div
|
|
101
|
+
initial={{ opacity: 0, y: 3 }}
|
|
102
|
+
animate={{ opacity: 1, y: 0 }}
|
|
103
|
+
transition={{ duration: 0.2 }}
|
|
104
|
+
className={clsx(styles.tooltip, styles[size])}
|
|
105
|
+
>
|
|
106
|
+
{heading && (
|
|
107
|
+
<div className={styles.heading}>
|
|
108
|
+
{headingIcon === null ? null : headingIcon || (
|
|
109
|
+
<Icon icon={Info} size={14} className={styles.icon} />
|
|
110
|
+
)}
|
|
111
|
+
<TextWhenString as="p" kind="paragraphXSmall" weight="medium">
|
|
112
|
+
{heading}
|
|
113
|
+
</TextWhenString>
|
|
114
|
+
</div>
|
|
115
|
+
)}
|
|
116
|
+
<TextWhenString as="p" kind="paragraphXSmall">
|
|
117
|
+
{children}
|
|
118
|
+
</TextWhenString>
|
|
119
|
+
</motion.div>
|
|
120
|
+
</RadixTooltip.Content>
|
|
121
|
+
</RadixTooltip.Portal>
|
|
122
|
+
</RadixTooltip.Root>
|
|
123
|
+
</RadixTooltip.Provider>
|
|
124
|
+
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './InformationalTooltip';
|
|
@@ -71,14 +71,14 @@
|
|
|
71
71
|
|
|
72
72
|
transition: var(--pte-animations-interaction);
|
|
73
73
|
|
|
74
|
-
&:hover, &[data-headlessui-state="active"] {
|
|
75
|
-
background-color: var(--pte-new-colors-overlayMedium);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
74
|
&[data-selected=true] {
|
|
79
75
|
background-color: var(--pte-new-colors-overlaySubtle);
|
|
80
76
|
}
|
|
81
77
|
|
|
78
|
+
&:hover, &[data-headlessui-state="active"] {
|
|
79
|
+
background-color: var(--pte-new-colors-overlayMedium);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
82
|
&[data-status="disabled"], &[data-headlessui-state~="disabled"] {
|
|
83
83
|
pointer-events: none;
|
|
84
84
|
cursor: default;
|
|
@@ -239,3 +239,71 @@
|
|
|
239
239
|
padding: 4px 10px;
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
|
+
|
|
243
|
+
.segmentedContainer {
|
|
244
|
+
display: flex;
|
|
245
|
+
flex-direction: row;
|
|
246
|
+
justify-content: space-between;
|
|
247
|
+
align-items: center;
|
|
248
|
+
background-color: var(--pte-new-colors-surfaceTertiary);
|
|
249
|
+
border-radius: var(--pte-new-borders-radius-roundedMedium);
|
|
250
|
+
padding: 4px;
|
|
251
|
+
gap: 2px;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.segmentedOption {
|
|
255
|
+
padding: 3px 20px;
|
|
256
|
+
border-radius: var(--pte-new-borders-radius-roundedSmall);
|
|
257
|
+
width: 100%;
|
|
258
|
+
color: var(--pte-new-colors-contentTertiary);
|
|
259
|
+
display: flex;
|
|
260
|
+
flex-direction: row;
|
|
261
|
+
justify-content: center;
|
|
262
|
+
align-items: center;
|
|
263
|
+
position: relative;
|
|
264
|
+
transition: var(--pte-animations-interaction);
|
|
265
|
+
|
|
266
|
+
&.tall {
|
|
267
|
+
padding: 6px 20px;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
&[data-headlessui-state~="active"] {
|
|
271
|
+
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
&[data-status="disabled"], &[data-headlessui-state~="disabled"] {
|
|
275
|
+
pointer-events: none;
|
|
276
|
+
cursor: default;
|
|
277
|
+
|
|
278
|
+
&, & * {
|
|
279
|
+
color: var(--pte-new-colors-contentDisabled);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
//.cardSurface {
|
|
283
|
+
// background-color: var(--pte-new-colors-overlayWhiteSubtle);
|
|
284
|
+
// border-color: var(--pte-new-colors-borderStrong);
|
|
285
|
+
//}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
&:hover {
|
|
289
|
+
background-color: var(--pte-new-colors-surfaceSecondary);
|
|
290
|
+
color: var(--pte-new-colors-contentSecondary);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
&[data-headlessui-state~="checked"] {
|
|
294
|
+
color: var(--pte-new-colors-contentPrimary);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.segmentedBackground {
|
|
299
|
+
position: absolute;
|
|
300
|
+
height: 100%;
|
|
301
|
+
left: 0;
|
|
302
|
+
right: 0;
|
|
303
|
+
background-color: var(--pte-new-colors-surfaceQuaternary);
|
|
304
|
+
border-radius: var(--pte-new-borders-radius-roundedSmall);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.segmentedText {
|
|
308
|
+
z-index: 1;
|
|
309
|
+
}
|
|
@@ -111,3 +111,17 @@ export const Card: Story = {
|
|
|
111
111
|
},
|
|
112
112
|
render,
|
|
113
113
|
};
|
|
114
|
+
|
|
115
|
+
export const Segmented: Story = {
|
|
116
|
+
args: {
|
|
117
|
+
label: 'Donation',
|
|
118
|
+
description: 'Select the frequency of your donation.',
|
|
119
|
+
kind: 'segmented',
|
|
120
|
+
options: [
|
|
121
|
+
{ id: '1', node: 'One Time' },
|
|
122
|
+
{ id: '2', node: 'Monthly' },
|
|
123
|
+
{ id: '3', node: 'Quarterly' },
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
render,
|
|
127
|
+
};
|
|
@@ -10,6 +10,7 @@ import { Listbox, RadioGroup, Transition } from '@headlessui/react';
|
|
|
10
10
|
import clsx from 'clsx';
|
|
11
11
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
12
12
|
import { faChevronDown } from '@fortawesome/free-solid-svg-icons';
|
|
13
|
+
import { motion } from 'framer-motion';
|
|
13
14
|
import inputStyles from '../input/Input.module.scss';
|
|
14
15
|
import dropdownStyles from '../utility/Dropdown.module.scss';
|
|
15
16
|
import styles from './Select.module.scss';
|
|
@@ -48,10 +49,10 @@ export type SelectProps<T = Record<string, any>> = {
|
|
|
48
49
|
*/
|
|
49
50
|
onChange?: (value: Option<T>['id'] | null) => void | Promise<void>;
|
|
50
51
|
/**
|
|
51
|
-
* The visual variant of the Select. `listbox` will render as a dropdown menu, `radio` will render as a radio group,
|
|
52
|
+
* The visual variant of the Select. `listbox` will render as a dropdown menu, `radio` will render as a radio group, `card` will render as selectable cards, and `segmented` will render as a segmented control.
|
|
52
53
|
* @default listbox
|
|
53
54
|
*/
|
|
54
|
-
kind?: 'listbox' | 'radio' | 'card';
|
|
55
|
+
kind?: 'listbox' | 'radio' | 'card' | 'segmented';
|
|
55
56
|
/**
|
|
56
57
|
* The size of the options dropdown, in pixels. Only applicable to kind="listbox".
|
|
57
58
|
*/
|
|
@@ -61,6 +62,11 @@ export type SelectProps<T = Record<string, any>> = {
|
|
|
61
62
|
* @default false
|
|
62
63
|
*/
|
|
63
64
|
hasOptionBorder?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Controls the height of the segment control. Only applicable to kind="segmented".
|
|
67
|
+
* @default compact
|
|
68
|
+
*/
|
|
69
|
+
segmentedHeight?: 'compact' | 'tall';
|
|
64
70
|
|
|
65
71
|
/**
|
|
66
72
|
* Prop overrides for other rendered elements. Overrides for the input itself should be passed directly to the component.
|
|
@@ -106,6 +112,7 @@ export const Select = forwardRef(function <T = Record<string, any>>({
|
|
|
106
112
|
kind = 'listbox',
|
|
107
113
|
maxHeight = 320,
|
|
108
114
|
hasOptionBorder = false,
|
|
115
|
+
segmentedHeight = 'compact',
|
|
109
116
|
overrides,
|
|
110
117
|
}: SelectProps<T>, ref: ForwardedRef<any>) {
|
|
111
118
|
const inputID = useId();
|
|
@@ -263,6 +270,37 @@ export const Select = forwardRef(function <T = Record<string, any>>({
|
|
|
263
270
|
))}
|
|
264
271
|
</RadioGroup>
|
|
265
272
|
)}
|
|
273
|
+
{kind === 'segmented' && (
|
|
274
|
+
<RadioGroup ref={ref} as="div" className={styles.segmentedContainer} value={value || options[0].id} onChange={onChange}>
|
|
275
|
+
{options.map((option) => (
|
|
276
|
+
<RadioGroup.Option
|
|
277
|
+
as="div"
|
|
278
|
+
className={clsx(
|
|
279
|
+
styles.segmentedOption,
|
|
280
|
+
styles[segmentedHeight],
|
|
281
|
+
)}
|
|
282
|
+
key={option.id}
|
|
283
|
+
value={option.id}
|
|
284
|
+
disabled={option.disabled || false}
|
|
285
|
+
data-status={disabled ? 'disabled' : (status || 'default')}
|
|
286
|
+
>
|
|
287
|
+
{(option.id === value || (!value && option.id === options[0].id)) && (
|
|
288
|
+
<motion.div
|
|
289
|
+
className={styles.segmentedBackground}
|
|
290
|
+
layoutId={`${inputID}-segmented-selected`}
|
|
291
|
+
transition={{
|
|
292
|
+
ease: [0.42, 0.0, 0.58, 1.0],
|
|
293
|
+
duration: 0.25,
|
|
294
|
+
}}
|
|
295
|
+
/>
|
|
296
|
+
)}
|
|
297
|
+
<TextWhenString kind="paragraphXSmall" weight="medium" className={styles.segmentedText}>
|
|
298
|
+
{option.node}
|
|
299
|
+
</TextWhenString>
|
|
300
|
+
</RadioGroup.Option>
|
|
301
|
+
))}
|
|
302
|
+
</RadioGroup>
|
|
303
|
+
)}
|
|
266
304
|
</Field>
|
|
267
305
|
);
|
|
268
306
|
});
|
|
@@ -106,6 +106,7 @@ export type Theme = {
|
|
|
106
106
|
contentNegative: CSSColor,
|
|
107
107
|
contentWarning: CSSColor,
|
|
108
108
|
contentPositive: CSSColor,
|
|
109
|
+
contentLink: CSSColor,
|
|
109
110
|
|
|
110
111
|
// Content Inverse
|
|
111
112
|
contentInversePrimary: CSSColor,
|
|
@@ -671,6 +672,7 @@ export const LightTheme: Theme = {
|
|
|
671
672
|
contentNegative: T.new.colors.red400,
|
|
672
673
|
contentWarning: T.new.colors.yellow500,
|
|
673
674
|
contentPositive: T.new.colors.green400,
|
|
675
|
+
contentLink: T.new.colors.blue500,
|
|
674
676
|
|
|
675
677
|
// Content Inverse
|
|
676
678
|
contentInversePrimary: T.new.colors.white,
|
|
@@ -1318,6 +1320,7 @@ export const DarkTheme: Theme = merge(LightTheme, {
|
|
|
1318
1320
|
contentSecondary: T.new.colors.grey800,
|
|
1319
1321
|
contentTertiary: T.new.colors.grey1000,
|
|
1320
1322
|
contentDisabled: T.new.colors.grey1300,
|
|
1323
|
+
contentLink: T.new.colors.blue400,
|
|
1321
1324
|
|
|
1322
1325
|
contentInversePrimary: T.new.colors.grey2400,
|
|
1323
1326
|
contentInverseSecondary: T.new.colors.grey1600,
|
|
@@ -125,6 +125,9 @@ export const ColorsNew = {
|
|
|
125
125
|
teal700: '#004137',
|
|
126
126
|
teal800: '#002620',
|
|
127
127
|
|
|
128
|
+
blue400: '#7a96fc',
|
|
129
|
+
blue500: '#3760f5',
|
|
130
|
+
|
|
128
131
|
gradientTeal: 'linear-gradient(95.14deg, #26EDED 0%, #14EFAC 100%)',
|
|
129
132
|
gradientOrange: 'linear-gradient(95.14deg, #FF814A 0%, #FFA800 100%)',
|
|
130
133
|
gradientPink: 'linear-gradient(90deg, #FF4BED 0%, #F6418D 100%)',
|