Skip to content

Commit b08ed3a

Browse files
authored
feat(ui-components) Add button component (jitsi#11868)
1 parent 718d329 commit b08ed3a

21 files changed

Lines changed: 365 additions & 349 deletions

File tree

‎custom.d.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
declare module '*.svg' {
2+
const content: any;
3+
export default content;
4+
}

‎react/.eslintrc.js‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ module.exports = {
1717
'@typescript-eslint/no-empty-function': 'off',
1818
'@typescript-eslint/ban-types': 'off',
1919
'@typescript-eslint/no-explicit-any': 'off',
20-
'no-prototype-builtins': 'off'
20+
'no-prototype-builtins': 'off',
21+
'no-shadow': 'off',
22+
'@typescript-eslint/no-shadow': [ 'error' ]
2123
},
2224
'plugins': [ '@typescript-eslint' ],
2325
'extends': [
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import { makeStyles } from '@material-ui/core';
2+
import clsx from 'clsx';
3+
import React from 'react';
4+
5+
import Icon from '../../icons/components/Icon';
6+
import { BUTTON_TYPES } from '../../react/constants';
7+
import { withPixelLineHeight } from '../../styles/functions.web';
8+
9+
import { ButtonProps } from './types';
10+
11+
interface IButtonProps extends ButtonProps {
12+
13+
/**
14+
* Whether or not the button should be full width.
15+
*/
16+
fullWidth?: boolean,
17+
18+
/**
19+
* The id of the button.
20+
*/
21+
id?: string;
22+
23+
/**
24+
* Click callback.
25+
*/
26+
onClick: () => void;
27+
28+
/**
29+
* Which size the button should be.
30+
*/
31+
size?: 'small' | 'medium' | 'large';
32+
}
33+
34+
const useStyles = makeStyles((theme: any) => {
35+
return {
36+
button: {
37+
backgroundColor: theme.palette.action01,
38+
color: theme.palette.text01,
39+
borderRadius: theme.shape.borderRadius,
40+
padding: '10px 16px',
41+
display: 'flex',
42+
alignItems: 'center',
43+
justifyContent: 'center',
44+
border: 0,
45+
...withPixelLineHeight(theme.typography.bodyShortBold),
46+
transition: 'background .2s',
47+
cursor: 'pointer',
48+
49+
'&:hover': {
50+
backgroundColor: theme.palette.action01Hover
51+
},
52+
53+
'&:active': {
54+
backgroundColor: theme.palette.action01Active
55+
},
56+
57+
'&:focus': {
58+
outline: 0,
59+
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`
60+
},
61+
62+
'& svg': {
63+
fill: theme.palette.icon01
64+
}
65+
},
66+
67+
primary: {},
68+
69+
secondary: {
70+
backgroundColor: theme.palette.action02,
71+
color: theme.palette.text04,
72+
73+
'&:hover': {
74+
backgroundColor: theme.palette.action02Hover
75+
},
76+
77+
'&:active': {
78+
backgroundColor: theme.palette.action02Active
79+
},
80+
81+
'& svg': {
82+
fill: theme.palette.icon04
83+
}
84+
},
85+
86+
tertiary: {
87+
backgroundColor: theme.palette.action03,
88+
89+
'&:hover': {
90+
backgroundColor: theme.palette.action03Hover
91+
},
92+
93+
'&:active': {
94+
backgroundColor: theme.palette.action03Active
95+
}
96+
},
97+
98+
destructive: {
99+
backgroundColor: theme.palette.actionDanger,
100+
101+
'&:hover': {
102+
backgroundColor: theme.palette.actionDangerHover
103+
},
104+
105+
'&:active': {
106+
backgroundColor: theme.palette.actionDangerActive
107+
}
108+
},
109+
110+
disabled: {
111+
backgroundColor: theme.palette.disabled01,
112+
color: theme.palette.text03,
113+
114+
'&:hover': {
115+
backgroundColor: theme.palette.disabled01,
116+
color: theme.palette.text03
117+
},
118+
119+
'&:active': {
120+
backgroundColor: theme.palette.disabled01,
121+
color: theme.palette.text03
122+
},
123+
124+
'& svg': {
125+
fill: theme.palette.icon03
126+
}
127+
},
128+
129+
iconButton: {
130+
padding: '10px'
131+
},
132+
133+
textWithIcon: {
134+
marginLeft: `${theme.spacing(2)}px`
135+
},
136+
137+
small: {
138+
padding: '8px 16px',
139+
...withPixelLineHeight(theme.typography.labelBold),
140+
141+
'&.iconButton': {
142+
padding: '6px'
143+
}
144+
},
145+
146+
medium: {},
147+
148+
large: {
149+
padding: '13px 16px',
150+
...withPixelLineHeight(theme.typography.bodyShortBoldLarge),
151+
152+
'&.iconButton': {
153+
padding: '14px'
154+
}
155+
},
156+
157+
fullWidth: {
158+
width: '100%'
159+
}
160+
};
161+
});
162+
163+
const Button = ({
164+
accessibilityLabel,
165+
disabled,
166+
fullWidth,
167+
icon,
168+
id,
169+
label,
170+
onClick,
171+
size = 'medium',
172+
type = BUTTON_TYPES.PRIMARY
173+
}: IButtonProps) => {
174+
const styles = useStyles();
175+
176+
return (
177+
<button
178+
aria-label = { accessibilityLabel }
179+
className = { clsx(styles.button, styles[type],
180+
disabled && styles.disabled,
181+
icon && !label && `${styles.iconButton} iconButton`,
182+
styles[size], fullWidth && styles.fullWidth) }
183+
disabled = { disabled }
184+
{ ...(id ? { id } : {}) }
185+
onClick = { onClick }
186+
type = 'button'>
187+
{icon && <Icon
188+
size = { 20 }
189+
src = { icon } />}
190+
{label && <span className = { icon ? styles.textWithIcon : '' }>{label}</span>}
191+
</button>
192+
);
193+
};
194+
195+
export default Button;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { BUTTON_TYPES } from '../../react/constants';
2+
3+
export interface ButtonProps {
4+
5+
/**
6+
* Label used for accessibility.
7+
*/
8+
accessibilityLabel: string;
9+
10+
/**
11+
* Whether or not the button is disabled.
12+
*/
13+
disabled?: boolean;
14+
15+
/**
16+
* The icon to be displayed on the button.
17+
*/
18+
icon?: Function;
19+
20+
/**
21+
* The text to be displayed on the button.
22+
*/
23+
label?: string;
24+
25+
/**
26+
* The type of button to be displayed.
27+
*/
28+
type?: BUTTON_TYPES;
29+
}

react/features/base/icons/components/Icon.js renamed to react/features/base/icons/components/Icon.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
// @flow
2-
1+
/* eslint-disable import/order */
32
import React, { useCallback } from 'react';
43

4+
// @ts-ignore
55
import { Container } from '../../react/base';
6+
7+
// @ts-ignore
68
import { styleTypeToObject } from '../../styles';
79

810
type Props = {
@@ -15,7 +17,7 @@ type Props = {
1517
/**
1618
* Color of the icon (if not provided by the style object).
1719
*/
18-
color?: ?string,
20+
color?: string,
1921

2022
/**
2123
* Id prop (mainly for autotests).
@@ -35,7 +37,7 @@ type Props = {
3537
/**
3638
* The size of the icon (if not provided by the style object).
3739
*/
38-
size?: ?number | string,
40+
size?: number | string,
3941

4042
/**
4143
* The preloaded icon component to render.
@@ -82,12 +84,12 @@ type Props = {
8284
*/
8385
ariaControls?: string,
8486

85-
/**
87+
/**
8688
* TabIndex for the Icon.
8789
*/
8890
tabIndex?: number,
8991

90-
/**
92+
/**
9193
* Role for the Icon.
9294
*/
9395
role?: string,
@@ -110,7 +112,7 @@ export const DEFAULT_SIZE = navigator.product === 'ReactNative' ? 36 : 22;
110112
* Implements an Icon component that takes a loaded SVG file as prop and renders it as an icon.
111113
*
112114
* @param {Props} props - The props of the component.
113-
* @returns {Reactelement}
115+
* @returns {ReactElement}
114116
*/
115117
export default function Icon(props: Props) {
116118
const {
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// @flow
2-
31
export { default as IconAdd } from './add.svg';
42
export { default as IconAddPeople } from './link.svg';
53
export { default as IconArrowBack } from './arrow_back.svg';

‎react/features/base/react/components/native/Button.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ import {
1111
import BaseTheme from '../../../ui/components/BaseTheme.native';
1212
// @ts-ignore
1313
import { BUTTON_MODES, BUTTON_TYPES } from '../../constants';
14-
import { ButtonProps } from '../../types';
14+
import { IButtonProps } from '../../types';
1515

1616
// @ts-ignore
1717
import styles from './styles';
1818

1919

20-
const Button: React.FC<ButtonProps> = ({
20+
const Button: React.FC<IButtonProps> = ({
2121
accessibilityLabel,
2222
color: buttonColor,
2323
disabled,
@@ -27,7 +27,7 @@ const Button: React.FC<ButtonProps> = ({
2727
onPress,
2828
style,
2929
type
30-
}: ButtonProps) => {
30+
}: IButtonProps) => {
3131
const { t } = useTranslation();
3232
const { CONTAINED } = BUTTON_MODES;
3333
const { DESTRUCTIVE, PRIMARY, SECONDARY, TERTIARY } = BUTTON_TYPES;
Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// @flow
2-
31
/**
42
* Z-index for components that are to be rendered like an overlay, to be over
53
* everything, such as modal-type of components, or dialogs.
@@ -9,12 +7,12 @@ export const OVERLAY_Z_INDEX = 1000;
97
/**
108
* The types of the buttons.
119
*/
12-
export const BUTTON_TYPES = {
13-
PRIMARY: 'primary',
14-
SECONDARY: 'secondary',
15-
TERTIARY: 'tertiary',
16-
DESTRUCTIVE: 'destructive'
17-
};
10+
export enum BUTTON_TYPES {
11+
PRIMARY = 'primary',
12+
SECONDARY = 'secondary',
13+
TERTIARY = 'tertiary',
14+
DESTRUCTIVE = 'destructive'
15+
}
1816

1917
/**
2018
* The modes of the buttons.

‎react/features/base/react/types.ts‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
export interface ButtonProps {
2-
accessibilityLabel?: string;
1+
import { ButtonProps } from '../components/common/types';
2+
3+
export interface IButtonProps extends ButtonProps {
34
color?: string;
4-
disabled?: boolean;
5-
icon?: JSX.Element;
6-
label?: string;
75
labelStyle?: Object|undefined;
86
onPress?: Function;
97
style?: Object|undefined;
10-
type?: string;
118
}
129

1310
export interface IconButtonProps {

react/features/base/styles/functions.web.js renamed to react/features/base/styles/functions.web.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
// @flow
2-
3-
import { type StyleType } from './functions.any';
1+
// @ts-ignore
2+
import { StyleType } from './functions.any';
43

4+
// @ts-ignore
55
export * from './functions.any';
66

77
/**
@@ -32,7 +32,7 @@ export function getFixedPlatformStyle(style: StyleType): StyleType {
3232
* @param {Object} base - The base object containing the `lineHeight` property.
3333
* @returns {Object}
3434
*/
35-
export function withPixelLineHeight(base: Object): Object {
35+
export function withPixelLineHeight(base: any): Object {
3636
return {
3737
...base,
3838
lineHeight: `${base.lineHeight}px`

0 commit comments

Comments
 (0)