4.3 KB157 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import type {ForwardedRef} from 'react';
9import type {ExclusiveOr} from './Types';
10import type {ReactProps} from './utils';
11
12import * as stylex from '@stylexjs/stylex';
13import {forwardRef, type ReactNode} from 'react';
14import {layout} from './theme/layout';
15import {colors} from './theme/tokens.stylex';
16
17/**
18 * StyleX tries to evaluate CSS variables and store them separately.
19 * Use a layer of indirection so the CSS variable is used literally.
20 */
21export const vars = {
22 fg: 'var(--foreground)',
23 border: 'var(--contrast-border)',
24 /** very bright border, usually only set in high-contrast themes */
25 activeBorder: 'var(--contrast-active-border, transparent)',
26 focusBorder: 'var(--focus-border, transparent)',
27};
28
29export const buttonStyles = stylex.create({
30 button: {
31 background: {
32 default: 'var(--button-secondary-background)',
33 ':hover': 'var(--button-secondary-hover-background)',
34 },
35 color: 'var(--button-secondary-foreground)',
36 border: '1px solid var(--button-border)',
37 borderRadius: '2px',
38 padding: '4px 11px',
39 fontFamily: 'var(--font-family)',
40 lineHeight: '16px',
41 cursor: 'pointer',
42 gap: '8px',
43 outlineOffset: '2px',
44 outlineStyle: 'solid',
45 outlineWidth: '1px',
46 outlineColor: {
47 default: 'transparent',
48 ':focus-visible': vars.focusBorder,
49 },
50 flexWrap: 'nowrap',
51 whiteSpace: 'nowrap',
52 },
53 primary: {
54 background: {
55 default: 'var(--button-primary-background)',
56 ':hover': 'var(--button-primary-hover-background)',
57 },
58 color: 'var(--button-primary-foreground)',
59 },
60 icon: {
61 border: '1px solid',
62 borderColor: colors.subtleHoverDarken,
63 background: {
64 default: colors.subtleHoverDarken,
65 ':hover': 'var(--button-icon-hover-background, rgba(90, 93, 94, 0.31))',
66 },
67 borderRadius: '5px',
68 color: vars.fg,
69 padding: '3px',
70 outlineStyle: {
71 default: 'solid',
72 ':hover': 'dotted',
73 ':focus-within': 'solid',
74 },
75 outlineOffset: 0,
76 outlineColor: {
77 default: 'transparent',
78 ':hover': vars.activeBorder,
79 ':focus-visible': vars.focusBorder,
80 },
81 },
82 disabled: {
83 opacity: '0.4',
84 cursor: 'not-allowed',
85 },
86});
87
88export const Button = forwardRef(
89 (
90 {
91 icon: iconProp,
92 primary: primaryProp,
93 disabled,
94 onClick,
95 children,
96 xstyle,
97 kind,
98 className,
99 ...rest
100 }: {
101 className?: string;
102 children?: ReactNode;
103 disabled?: boolean;
104 xstyle?: stylex.StyleXStyles;
105 primary?: boolean;
106 icon?: boolean;
107 } & Omit<ReactProps<HTMLButtonElement>, 'className'> &
108 ExclusiveOr<
109 ExclusiveOr<
110 {
111 /**
112 * Render as a bright button, encouraged the primary confirmation action.
113 * Equivalent to kind='primary'.
114 */
115 primary?: boolean;
116 },
117 {
118 /**
119 * Render as a smaller, more subtle button. Useful in toolbars or when using an icon instead of a label.
120 * Equivalent to kind='icon'.
121 */
122 icon?: boolean;
123 }
124 >,
125 /** How to display the button. Can also provide `primary` or `icon` shorthand bool props instead. */
126 {kind?: 'primary' | 'icon' | undefined}
127 >,
128 ref: ForwardedRef<HTMLButtonElement>,
129 ) => {
130 const primary = kind === 'primary' || primaryProp === true;
131 const icon = kind === 'icon' || iconProp === true;
132 const {className: stylexClassName, ...otherStylex} = stylex.props(
133 layout.flexRow,
134 buttonStyles.button,
135 primary && buttonStyles.primary,
136 icon && buttonStyles.icon,
137 disabled && buttonStyles.disabled,
138 xstyle,
139 );
140 return (
141 <button
142 tabIndex={disabled ? -1 : 0}
143 onClick={e => {
144 // don't allow clicking a disabled button
145 disabled !== true && onClick?.(e);
146 }}
147 ref={ref}
148 className={stylexClassName + (className ? ' ' + className : '')}
149 {...otherStylex}
150 disabled={disabled}
151 {...rest}>
152 {children}
153 </button>
154 );
155 },
156);
157