Migrate twenty ui to linaria (#18307)
## Migrate twenty-ui from Emotion to Linaria
Completes the migration of all `twenty-ui` components from Emotion
(runtime CSS-in-JS) to Linaria (zero-runtime, CSS extracted at build
time).
- Replaced `@emotion/styled` with `@linaria/react` across ~170 files
- Removed all Emotion dependencies from `twenty-ui`
- Introduced a CSS custom properties-based theme system:
`themeCssVariables` where every leaf is a `var(--t-xxx)` reference,
injected onto `document.documentElement` by
`ThemeCssVariableInjectorEffect`
- No more `theme` prop threading — styled components reference
`themeCssVariables.x.y` directly at build time
- Updated `twenty-front` consumers to remove `theme={theme}` prop
passing
**Before / After:**
```tsx
// Emotion
color: ${({ theme }) => theme.font.color.primary};
padding: ${({ theme }) => theme.spacing(4)};
// Linaria
color: ${themeCssVariables.font.color.primary};
padding: ${themeCssVariables.spacing[4]};
```
### Theme architecture
Two build-time utilities produce the theme system:
- **`buildThemeReferencingRootCssVariables`** — walks the theme object
and builds a nested mirror where every leaf is a `var(--t-xxx)` string
(evaluated at build time by wyw-in-js)
- **`prepareThemeForRootCssVariableInjection`** — walks the runtime
theme and collects flat `[--css-variable-name, value]` pairs, injected
onto `document.documentElement` by `ThemeCssVariableInjectorEffect`
Both share naming conventions (`camelToKebab`, `SPACING_VALUES`,
`formatSpacingKey`) and are unit tested.
### Spacing cleanup
Spacing scale now uses integers 0–32 (generated via loop), with `0.5`
and `1.5` as the only fractional exceptions. All other fractional
spacing usages (`0.25`, `0.75`, `1.25`, `2.5`, `3.5`) were replaced with
literal pixel values across ~20 twenty-front files.
### Framer Motion integration
Linaria doesn't support `styled(motion.div)` — wrapping a motion element
with `styled()` causes the component body to be stripped at build time.
Instead, we define the styled component first, then wrap it with
`motion.create()`:
```tsx
const StyledBarBase = styled.div`
background-color: ${themeCssVariables.font.color.primary};
height: 100%;
`;
const StyledBar = motion.create(StyledBarBase);
```
### Block interpolations
Linaria doesn't support interpolations that return multiple CSS
declarations (Linaria wraps the entire block in a single `var()`,
producing invalid CSS). These were split into individual property
interpolations:
```tsx
// Emotion — single interpolation returning multiple declarations
border-left: ${({ divider, theme }) => {
const border = `1px solid ${theme.border.color.light}`;
return divider ? `border-${divider}: ${border}` : '';
}}
// Linaria — one interpolation per property
border-left: ${({ divider }) =>
divider === 'left' ? `1px solid ${themeCssVariables.border.color.light}` : 'none'};
border-right: ${({ divider }) =>
divider === 'right' ? `1px solid ${themeCssVariables.border.color.light}` : 'none'};
```
### Dynamic styles via CSS variables
When a component needs to compute styles from multiple props with
complex branching logic (e.g. `Button` combining `variant`, `accent`,
`inverted`, `disabled`, `focus`, `position`), Linaria's prop
interpolations become unwieldy. In those cases we use a
`computeDynamicStyles` function that returns a `CSSProperties` object
injected via `style={}`, referenced from the static CSS with `var()`:
```tsx
const StyledButton = styled.button`
background: var(--btn-bg);
border-color: var(--btn-border-color);
&:hover { background: var(--btn-hover-bg); }
`;
const dynamicStyles = useMemo(() => {
const s = computeButtonDynamicStyles(variant, accent, ...);
return { '--btn-bg': s.background, '--btn-hover-bg': s.hoverBackground } as CSSProperties;
}, [variant, accent, ...]);
return <StyledButton style={dynamicStyles} />;
```
### CSS var + unit concatenation
CSS custom properties can't be concatenated with unit suffixes directly
(`var(--x)px` is invalid). Values that need units use `calc()`:
```tsx
// Broken
transition: background ${themeCssVariables.animation.duration.instant}s ease;
// Fixed
transition: background calc(${themeCssVariables.animation.duration.instant} * 1s) ease;
```
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
import { css, useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useIsMobile } from '@ui/utilities';
|
||||
import { getOsShortcutSeparator } from '@ui/utilities/device/getOsShortcutSeparator';
|
||||
import { type MotionProps, motion } from 'framer-motion';
|
||||
import React, { useContext, useMemo } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { Pill } from '@ui/components/Pill/Pill';
|
||||
import { ThemeContext, themeCssVariables } from '@ui/theme';
|
||||
import {
|
||||
type ButtonAccent,
|
||||
type ButtonPosition,
|
||||
type ButtonProps,
|
||||
type ButtonSize,
|
||||
type ButtonVariant,
|
||||
@@ -19,16 +21,294 @@ export type AnimatedButtonProps = ButtonProps &
|
||||
soonLabel?: string;
|
||||
};
|
||||
|
||||
type AnimatedButtonDynamicStyles = {
|
||||
background: string;
|
||||
borderColor: string;
|
||||
borderWidthOverride: string;
|
||||
boxShadow: string;
|
||||
color: string;
|
||||
hoverBackground: string;
|
||||
activeBackground: string;
|
||||
};
|
||||
|
||||
const computeAnimatedButtonDynamicStyles = (
|
||||
variant: ButtonVariant,
|
||||
inverted: boolean,
|
||||
accent: ButtonAccent,
|
||||
disabled: boolean,
|
||||
focus: boolean,
|
||||
position: ButtonPosition,
|
||||
): AnimatedButtonDynamicStyles => {
|
||||
const result: AnimatedButtonDynamicStyles = {
|
||||
background: 'transparent',
|
||||
borderColor: 'transparent',
|
||||
borderWidthOverride: '',
|
||||
boxShadow: 'none',
|
||||
color: themeCssVariables.font.color.secondary,
|
||||
hoverBackground: 'transparent',
|
||||
activeBackground: 'transparent',
|
||||
};
|
||||
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
switch (accent) {
|
||||
case 'default':
|
||||
result.background = !inverted
|
||||
? themeCssVariables.background.secondary
|
||||
: themeCssVariables.background.primary;
|
||||
result.borderColor = !inverted
|
||||
? !disabled && focus
|
||||
? themeCssVariables.color.blue
|
||||
: themeCssVariables.background.transparent.light
|
||||
: themeCssVariables.background.transparent.light;
|
||||
result.borderWidthOverride = '1px 1px 1px 1px';
|
||||
result.boxShadow =
|
||||
!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? themeCssVariables.accent.tertiary
|
||||
: themeCssVariables.background.transparent.medium
|
||||
}`
|
||||
: 'none';
|
||||
result.color = !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.font.color.secondary
|
||||
: themeCssVariables.font.color.extraLight
|
||||
: themeCssVariables.font.color.secondary;
|
||||
if (!disabled) {
|
||||
result.hoverBackground = !inverted
|
||||
? themeCssVariables.background.tertiary
|
||||
: themeCssVariables.background.secondary;
|
||||
result.activeBackground = !inverted
|
||||
? themeCssVariables.background.quaternary
|
||||
: themeCssVariables.background.tertiary;
|
||||
} else {
|
||||
result.hoverBackground = result.background;
|
||||
result.activeBackground = result.background;
|
||||
}
|
||||
break;
|
||||
case 'blue':
|
||||
result.background = !inverted
|
||||
? themeCssVariables.color.blue
|
||||
: themeCssVariables.background.primary;
|
||||
result.borderColor = !inverted
|
||||
? focus
|
||||
? themeCssVariables.color.blue
|
||||
: themeCssVariables.background.transparent.light
|
||||
: themeCssVariables.background.transparent.light;
|
||||
result.borderWidthOverride = '1px 1px 1px 1px';
|
||||
result.boxShadow =
|
||||
!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? themeCssVariables.accent.tertiary
|
||||
: themeCssVariables.background.transparent.medium
|
||||
}`
|
||||
: 'none';
|
||||
result.color = !inverted
|
||||
? themeCssVariables.grayScale.gray1
|
||||
: themeCssVariables.color.blue;
|
||||
if (!disabled) {
|
||||
result.hoverBackground = !inverted
|
||||
? themeCssVariables.color.blue10
|
||||
: themeCssVariables.background.secondary;
|
||||
result.activeBackground = !inverted
|
||||
? themeCssVariables.color.blue12
|
||||
: themeCssVariables.background.tertiary;
|
||||
} else {
|
||||
result.hoverBackground = result.background;
|
||||
result.activeBackground = result.background;
|
||||
}
|
||||
break;
|
||||
case 'danger':
|
||||
result.background = !inverted
|
||||
? themeCssVariables.color.red
|
||||
: themeCssVariables.background.primary;
|
||||
result.borderColor = !inverted
|
||||
? focus
|
||||
? themeCssVariables.color.red
|
||||
: themeCssVariables.background.transparent.light
|
||||
: themeCssVariables.background.transparent.light;
|
||||
result.borderWidthOverride = '1px 1px';
|
||||
result.boxShadow =
|
||||
!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? themeCssVariables.color.red3
|
||||
: themeCssVariables.background.transparent.medium
|
||||
}`
|
||||
: 'none';
|
||||
result.color = !inverted
|
||||
? themeCssVariables.background.primary
|
||||
: themeCssVariables.color.red;
|
||||
if (!disabled) {
|
||||
result.hoverBackground = !inverted
|
||||
? themeCssVariables.color.red8
|
||||
: themeCssVariables.background.secondary;
|
||||
result.activeBackground = !inverted
|
||||
? themeCssVariables.color.red10
|
||||
: themeCssVariables.background.tertiary;
|
||||
} else {
|
||||
result.hoverBackground = result.background;
|
||||
result.activeBackground = result.background;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'secondary':
|
||||
case 'tertiary':
|
||||
switch (accent) {
|
||||
case 'default':
|
||||
result.background = 'transparent';
|
||||
result.borderColor = !inverted
|
||||
? variant === 'secondary'
|
||||
? !disabled && focus
|
||||
? themeCssVariables.color.blue
|
||||
: themeCssVariables.background.transparent.medium
|
||||
: focus
|
||||
? themeCssVariables.color.blue
|
||||
: 'transparent'
|
||||
: variant === 'secondary'
|
||||
? focus || disabled
|
||||
? themeCssVariables.grayScale.gray1
|
||||
: themeCssVariables.background.transparent.primary
|
||||
: focus
|
||||
? themeCssVariables.grayScale.gray1
|
||||
: 'transparent';
|
||||
result.borderWidthOverride = '1px 1px 1px 1px';
|
||||
result.boxShadow =
|
||||
!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? themeCssVariables.accent.tertiary
|
||||
: themeCssVariables.background.transparent.medium
|
||||
}`
|
||||
: 'none';
|
||||
result.color = !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.font.color.secondary
|
||||
: themeCssVariables.font.color.extraLight
|
||||
: themeCssVariables.font.color.inverted;
|
||||
result.hoverBackground = !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.background.transparent.light
|
||||
: 'transparent'
|
||||
: themeCssVariables.background.transparent.light;
|
||||
result.activeBackground = !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.background.transparent.light
|
||||
: 'transparent'
|
||||
: themeCssVariables.background.transparent.medium;
|
||||
break;
|
||||
case 'blue':
|
||||
result.background = 'transparent';
|
||||
result.borderColor = !inverted
|
||||
? variant === 'secondary'
|
||||
? focus
|
||||
? themeCssVariables.color.blue
|
||||
: themeCssVariables.accent.primary
|
||||
: focus
|
||||
? themeCssVariables.color.blue
|
||||
: 'transparent'
|
||||
: variant === 'secondary'
|
||||
? focus || disabled
|
||||
? themeCssVariables.grayScale.gray1
|
||||
: themeCssVariables.background.transparent.primary
|
||||
: focus
|
||||
? themeCssVariables.grayScale.gray1
|
||||
: 'transparent';
|
||||
result.borderWidthOverride = '1px 1px 1px 1px';
|
||||
result.boxShadow =
|
||||
!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? themeCssVariables.accent.tertiary
|
||||
: themeCssVariables.background.transparent.medium
|
||||
}`
|
||||
: 'none';
|
||||
result.color = !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.color.blue
|
||||
: themeCssVariables.accent.accent4060
|
||||
: themeCssVariables.font.color.inverted;
|
||||
result.hoverBackground = !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.accent.tertiary
|
||||
: 'transparent'
|
||||
: themeCssVariables.background.transparent.light;
|
||||
result.activeBackground = !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.accent.secondary
|
||||
: 'transparent'
|
||||
: themeCssVariables.background.transparent.medium;
|
||||
break;
|
||||
case 'danger':
|
||||
result.background = 'transparent';
|
||||
result.borderColor = !inverted
|
||||
? variant === 'secondary'
|
||||
? focus
|
||||
? themeCssVariables.color.red
|
||||
: themeCssVariables.border.color.danger
|
||||
: focus
|
||||
? themeCssVariables.color.red
|
||||
: 'transparent'
|
||||
: variant === 'secondary'
|
||||
? focus || disabled
|
||||
? themeCssVariables.grayScale.gray1
|
||||
: themeCssVariables.background.transparent.primary
|
||||
: focus
|
||||
? themeCssVariables.grayScale.gray1
|
||||
: 'transparent';
|
||||
result.borderWidthOverride = '1px 1px 1px 1px';
|
||||
result.boxShadow =
|
||||
!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? themeCssVariables.color.red3
|
||||
: themeCssVariables.background.transparent.medium
|
||||
}`
|
||||
: 'none';
|
||||
result.color = !inverted
|
||||
? themeCssVariables.font.color.danger
|
||||
: themeCssVariables.font.color.inverted;
|
||||
result.hoverBackground = !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.background.danger
|
||||
: 'transparent'
|
||||
: themeCssVariables.background.transparent.light;
|
||||
result.activeBackground = !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.background.danger
|
||||
: 'transparent'
|
||||
: themeCssVariables.background.transparent.medium;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.borderWidthOverride !== '' && position !== 'standalone') {
|
||||
switch (position) {
|
||||
case 'left':
|
||||
result.borderWidthOverride = '1px 0px 1px 1px';
|
||||
break;
|
||||
case 'middle':
|
||||
result.borderWidthOverride = '1px 0px 1px 0px';
|
||||
break;
|
||||
case 'right':
|
||||
result.borderWidthOverride = '1px 1px 1px 0px';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const StyledButton = styled.button<
|
||||
Pick<
|
||||
ButtonProps,
|
||||
| 'fullWidth'
|
||||
| 'variant'
|
||||
| 'inverted'
|
||||
| 'size'
|
||||
| 'position'
|
||||
| 'accent'
|
||||
| 'focus'
|
||||
| 'justify'
|
||||
| 'to'
|
||||
| 'target'
|
||||
@@ -37,292 +317,44 @@ const StyledButton = styled.button<
|
||||
>
|
||||
>`
|
||||
align-items: center;
|
||||
${({ theme, variant, inverted, accent, disabled, focus }) => {
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
switch (accent) {
|
||||
case 'default':
|
||||
return css`
|
||||
background: ${!inverted
|
||||
? theme.background.secondary
|
||||
: theme.background.primary};
|
||||
border-color: ${!inverted
|
||||
? !disabled && focus
|
||||
? theme.color.blue
|
||||
: theme.background.transparent.light
|
||||
: theme.background.transparent.light};
|
||||
border-width: 1px 1px 1px 1px !important;
|
||||
box-shadow: ${!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? theme.accent.tertiary
|
||||
: theme.background.transparent.medium
|
||||
}`
|
||||
: 'none'};
|
||||
color: ${!inverted
|
||||
? !disabled
|
||||
? theme.font.color.secondary
|
||||
: theme.font.color.extraLight
|
||||
: theme.font.color.secondary};
|
||||
&:hover {
|
||||
background: ${!inverted
|
||||
? theme.background.tertiary
|
||||
: theme.background.secondary};
|
||||
}
|
||||
&:active {
|
||||
background: ${!inverted
|
||||
? theme.background.quaternary
|
||||
: theme.background.tertiary};
|
||||
}
|
||||
`;
|
||||
case 'blue':
|
||||
return css`
|
||||
background: ${!inverted
|
||||
? theme.color.blue
|
||||
: theme.background.primary};
|
||||
border-color: ${!inverted
|
||||
? focus
|
||||
? theme.color.blue
|
||||
: theme.background.transparent.light
|
||||
: theme.background.transparent.light};
|
||||
border-width: 1px 1px 1px 1px !important;
|
||||
box-shadow: ${!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? theme.accent.tertiary
|
||||
: theme.background.transparent.medium
|
||||
}`
|
||||
: 'none'};
|
||||
color: ${!inverted ? theme.grayScale.gray1 : theme.color.blue};
|
||||
${disabled
|
||||
? ''
|
||||
: css`
|
||||
&:hover {
|
||||
background: ${!inverted
|
||||
? theme.color.blue10
|
||||
: theme.background.secondary};
|
||||
}
|
||||
&:active {
|
||||
background: ${!inverted
|
||||
? theme.color.blue12
|
||||
: theme.background.tertiary};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
case 'danger':
|
||||
return css`
|
||||
background: ${!inverted
|
||||
? theme.color.red
|
||||
: theme.background.primary};
|
||||
border-color: ${!inverted
|
||||
? focus
|
||||
? theme.color.red
|
||||
: theme.background.transparent.light
|
||||
: theme.background.transparent.light};
|
||||
border-width: 1px 1px !important;
|
||||
box-shadow: ${!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? theme.color.red3
|
||||
: theme.background.transparent.medium
|
||||
}`
|
||||
: 'none'};
|
||||
color: ${!inverted ? theme.background.primary : theme.color.red};
|
||||
${disabled
|
||||
? ''
|
||||
: css`
|
||||
&:hover {
|
||||
background: ${!inverted
|
||||
? theme.color.red8
|
||||
: theme.background.secondary};
|
||||
}
|
||||
&:active {
|
||||
background: ${!inverted
|
||||
? theme.color.red10
|
||||
: theme.background.tertiary};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
}
|
||||
break;
|
||||
case 'secondary':
|
||||
case 'tertiary':
|
||||
switch (accent) {
|
||||
case 'default':
|
||||
return css`
|
||||
background: transparent;
|
||||
border-color: ${!inverted
|
||||
? variant === 'secondary'
|
||||
? !disabled && focus
|
||||
? theme.color.blue
|
||||
: theme.background.transparent.medium
|
||||
: focus
|
||||
? theme.color.blue
|
||||
: 'transparent'
|
||||
: variant === 'secondary'
|
||||
? focus || disabled
|
||||
? theme.grayScale.gray1
|
||||
: theme.background.transparent.primary
|
||||
: focus
|
||||
? theme.grayScale.gray1
|
||||
: 'transparent'};
|
||||
border-width: 1px 1px 1px 1px !important;
|
||||
box-shadow: ${!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? theme.accent.tertiary
|
||||
: theme.background.transparent.medium
|
||||
}`
|
||||
: 'none'};
|
||||
color: ${!inverted
|
||||
? !disabled
|
||||
? theme.font.color.secondary
|
||||
: theme.font.color.extraLight
|
||||
: theme.font.color.inverted};
|
||||
&:hover {
|
||||
background: ${!inverted
|
||||
? !disabled
|
||||
? theme.background.transparent.light
|
||||
: 'transparent'
|
||||
: theme.background.transparent.light};
|
||||
}
|
||||
&:active {
|
||||
background: ${!inverted
|
||||
? !disabled
|
||||
? theme.background.transparent.light
|
||||
: 'transparent'
|
||||
: theme.background.transparent.medium};
|
||||
}
|
||||
`;
|
||||
case 'blue':
|
||||
return css`
|
||||
background: transparent;
|
||||
border-color: ${!inverted
|
||||
? variant === 'secondary'
|
||||
? focus
|
||||
? theme.color.blue
|
||||
: theme.accent.primary
|
||||
: focus
|
||||
? theme.color.blue
|
||||
: 'transparent'
|
||||
: variant === 'secondary'
|
||||
? focus || disabled
|
||||
? theme.grayScale.gray1
|
||||
: theme.background.transparent.primary
|
||||
: focus
|
||||
? theme.grayScale.gray1
|
||||
: 'transparent'};
|
||||
border-width: 1px 1px 1px 1px !important;
|
||||
box-shadow: ${!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? theme.accent.tertiary
|
||||
: theme.background.transparent.medium
|
||||
}`
|
||||
: 'none'};
|
||||
color: ${!inverted
|
||||
? !disabled
|
||||
? theme.color.blue
|
||||
: theme.accent.accent4060
|
||||
: theme.font.color.inverted};
|
||||
&:hover {
|
||||
background: ${!inverted
|
||||
? !disabled
|
||||
? theme.accent.tertiary
|
||||
: 'transparent'
|
||||
: theme.background.transparent.light};
|
||||
}
|
||||
&:active {
|
||||
background: ${!inverted
|
||||
? !disabled
|
||||
? theme.accent.secondary
|
||||
: 'transparent'
|
||||
: theme.background.transparent.medium};
|
||||
}
|
||||
`;
|
||||
case 'danger':
|
||||
return css`
|
||||
background: transparent;
|
||||
border-color: ${!inverted
|
||||
? variant === 'secondary'
|
||||
? focus
|
||||
? theme.color.red
|
||||
: theme.border.color.danger
|
||||
: focus
|
||||
? theme.color.red
|
||||
: 'transparent'
|
||||
: variant === 'secondary'
|
||||
? focus || disabled
|
||||
? theme.grayScale.gray1
|
||||
: theme.background.transparent.primary
|
||||
: focus
|
||||
? theme.grayScale.gray1
|
||||
: 'transparent'};
|
||||
border-width: 1px 1px 1px 1px !important;
|
||||
box-shadow: ${!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? theme.color.red3
|
||||
: theme.background.transparent.medium
|
||||
}`
|
||||
: 'none'};
|
||||
color: ${!inverted
|
||||
? theme.font.color.danger
|
||||
: theme.font.color.inverted};
|
||||
&:hover {
|
||||
background: ${!inverted
|
||||
? !disabled
|
||||
? theme.background.danger
|
||||
: 'transparent'
|
||||
: theme.background.transparent.light};
|
||||
}
|
||||
&:active {
|
||||
background: ${!inverted
|
||||
? !disabled
|
||||
? theme.background.danger
|
||||
: 'transparent'
|
||||
: theme.background.transparent.medium};
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
}}
|
||||
background: var(--abtn-bg);
|
||||
border-color: var(--abtn-border-color);
|
||||
border-width: var(--abtn-border-width);
|
||||
box-shadow: var(--abtn-box-shadow);
|
||||
color: var(--abtn-color);
|
||||
|
||||
&:hover {
|
||||
background: var(--abtn-hover-bg);
|
||||
}
|
||||
&:active {
|
||||
background: var(--abtn-active-bg);
|
||||
}
|
||||
|
||||
text-decoration: none;
|
||||
border-radius: ${({ position, theme }) => {
|
||||
border-radius: ${({ position }) => {
|
||||
switch (position) {
|
||||
case 'left':
|
||||
return `${theme.border.radius.sm} 0px 0px ${theme.border.radius.sm}`;
|
||||
return `${themeCssVariables.border.radius.sm} 0px 0px ${themeCssVariables.border.radius.sm}`;
|
||||
case 'right':
|
||||
return `0px ${theme.border.radius.sm} ${theme.border.radius.sm} 0px`;
|
||||
return `0px ${themeCssVariables.border.radius.sm} ${themeCssVariables.border.radius.sm} 0px`;
|
||||
case 'middle':
|
||||
return '0px';
|
||||
case 'standalone':
|
||||
return theme.border.radius.sm;
|
||||
return themeCssVariables.border.radius.sm;
|
||||
}
|
||||
return '';
|
||||
}};
|
||||
border-style: solid;
|
||||
border-width: ${({ variant, position }) => {
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
case 'secondary':
|
||||
return position === 'middle' ? '1px 0px' : '1px';
|
||||
case 'tertiary':
|
||||
return '0';
|
||||
}
|
||||
}};
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
font-family: ${themeCssVariables.font.family};
|
||||
font-weight: 500;
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
font-size: ${themeCssVariables.font.size.md};
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
height: ${({ size }) => (size === 'small' ? '24px' : '32px')};
|
||||
justify-content: ${({ justify }) => justify};
|
||||
padding: ${({ theme }) => {
|
||||
return `0 ${theme.spacing(2)}`;
|
||||
}};
|
||||
justify-content: ${({ justify }) => justify ?? ''};
|
||||
padding: 0 ${themeCssVariables.spacing[2]};
|
||||
|
||||
transition: background 0.1s ease;
|
||||
|
||||
@@ -343,18 +375,20 @@ const StyledSeparator = styled.div<{
|
||||
buttonSize: ButtonSize;
|
||||
accent: ButtonAccent;
|
||||
}>`
|
||||
background: ${({ theme, accent }) => {
|
||||
background: ${({ accent }) => {
|
||||
switch (accent) {
|
||||
case 'blue':
|
||||
return theme.border.color.blue;
|
||||
return themeCssVariables.border.color.blue;
|
||||
case 'danger':
|
||||
return theme.border.color.danger;
|
||||
return themeCssVariables.border.color.danger;
|
||||
default:
|
||||
return theme.font.color.light;
|
||||
return themeCssVariables.font.color.light;
|
||||
}
|
||||
}};
|
||||
height: ${({ theme, buttonSize }) =>
|
||||
theme.spacing(buttonSize === 'small' ? 2 : 4)};
|
||||
height: ${({ buttonSize }) =>
|
||||
buttonSize === 'small'
|
||||
? themeCssVariables.spacing[2]
|
||||
: themeCssVariables.spacing[4]};
|
||||
margin: 0;
|
||||
width: 1px;
|
||||
`;
|
||||
@@ -363,19 +397,19 @@ const StyledShortcutLabel = styled.div<{
|
||||
variant: ButtonVariant;
|
||||
accent: ButtonAccent;
|
||||
}>`
|
||||
color: ${({ theme, variant, accent }) => {
|
||||
color: ${({ variant, accent }) => {
|
||||
switch (accent) {
|
||||
case 'blue':
|
||||
return theme.border.color.blue;
|
||||
return themeCssVariables.border.color.blue;
|
||||
case 'danger':
|
||||
return variant === 'primary'
|
||||
? theme.border.color.danger
|
||||
: theme.color.red8;
|
||||
? themeCssVariables.border.color.danger
|
||||
: themeCssVariables.color.red8;
|
||||
default:
|
||||
return theme.font.color.light;
|
||||
return themeCssVariables.font.color.light;
|
||||
}
|
||||
}};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
font-weight: ${themeCssVariables.font.weight.medium};
|
||||
`;
|
||||
|
||||
const StyledIconContainer = styled(motion.div)`
|
||||
@@ -411,8 +445,29 @@ export const AnimatedButton = ({
|
||||
dataGloballyPreventClickOutside,
|
||||
soonLabel = 'Soon',
|
||||
}: AnimatedButtonProps) => {
|
||||
const theme = useTheme();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const isMobile = useIsMobile();
|
||||
const isDisabled = soon || disabled;
|
||||
|
||||
const dynamicStyles = useMemo(() => {
|
||||
const s = computeAnimatedButtonDynamicStyles(
|
||||
variant,
|
||||
inverted,
|
||||
accent,
|
||||
isDisabled,
|
||||
focus,
|
||||
position,
|
||||
);
|
||||
return {
|
||||
'--abtn-bg': s.background,
|
||||
'--abtn-border-color': s.borderColor,
|
||||
'--abtn-border-width': s.borderWidthOverride || undefined,
|
||||
'--abtn-box-shadow': s.boxShadow,
|
||||
'--abtn-color': s.color,
|
||||
'--abtn-hover-bg': s.hoverBackground,
|
||||
'--abtn-active-bg': s.activeBackground,
|
||||
} as React.CSSProperties;
|
||||
}, [variant, inverted, accent, isDisabled, focus, position]);
|
||||
|
||||
const ButtonComponent = to ? Link : 'button';
|
||||
|
||||
@@ -420,15 +475,12 @@ export const AnimatedButton = ({
|
||||
<StyledButton
|
||||
as={ButtonComponent}
|
||||
fullWidth={fullWidth}
|
||||
variant={variant}
|
||||
inverted={inverted}
|
||||
size={size}
|
||||
position={position}
|
||||
disabled={soon || disabled}
|
||||
focus={focus}
|
||||
disabled={isDisabled}
|
||||
justify={justify}
|
||||
accent={accent}
|
||||
className={className}
|
||||
style={dynamicStyles}
|
||||
onClick={onClick}
|
||||
to={to}
|
||||
target={target}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type IconComponent } from '@ui/display';
|
||||
import {
|
||||
type LightIconButtonAccent,
|
||||
type LightIconButtonSize,
|
||||
} from '@ui/input/button/components/LightIconButton';
|
||||
import { ThemeContext, themeCssVariables } from '@ui/theme';
|
||||
import { motion, type MotionProps } from 'framer-motion';
|
||||
import { type ComponentProps, type MouseEvent } from 'react';
|
||||
import { type ComponentProps, type MouseEvent, useContext } from 'react';
|
||||
|
||||
export type AnimatedLightIconButtonProps = {
|
||||
className?: string;
|
||||
@@ -29,37 +29,38 @@ const StyledButton = styled.button<
|
||||
background: transparent;
|
||||
border: none;
|
||||
|
||||
border: ${({ disabled, theme, focus }) =>
|
||||
!disabled && focus ? `1px solid ${theme.color.blue}` : 'none'};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
box-shadow: ${({ disabled, theme, focus }) =>
|
||||
!disabled && focus ? `0 0 0 3px ${theme.color.blue3}` : 'none'};
|
||||
color: ${({ theme, accent, active, disabled, focus }) => {
|
||||
border: ${({ disabled, focus }) =>
|
||||
!disabled && focus ? `1px solid ${themeCssVariables.color.blue}` : 'none'};
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
box-shadow: ${({ disabled, focus }) =>
|
||||
!disabled && focus ? `0 0 0 3px ${themeCssVariables.color.blue3}` : 'none'};
|
||||
color: ${({ accent, active, disabled, focus }) => {
|
||||
switch (accent) {
|
||||
case 'secondary':
|
||||
return active || focus
|
||||
? theme.color.blue
|
||||
? themeCssVariables.color.blue
|
||||
: !disabled
|
||||
? theme.font.color.secondary
|
||||
: theme.font.color.extraLight;
|
||||
? themeCssVariables.font.color.secondary
|
||||
: themeCssVariables.font.color.extraLight;
|
||||
case 'tertiary':
|
||||
return active || focus
|
||||
? theme.color.blue
|
||||
? themeCssVariables.color.blue
|
||||
: !disabled
|
||||
? theme.font.color.tertiary
|
||||
: theme.font.color.extraLight;
|
||||
? themeCssVariables.font.color.tertiary
|
||||
: themeCssVariables.font.color.extraLight;
|
||||
}
|
||||
return '';
|
||||
}};
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
font-family: ${themeCssVariables.font.family};
|
||||
font-weight: ${themeCssVariables.font.weight.regular};
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
height: ${({ size }) => (size === 'small' ? '24px' : '32px')};
|
||||
justify-content: center;
|
||||
padding: ${({ theme }) => theme.spacing(1)};
|
||||
padding: ${themeCssVariables.spacing[1]};
|
||||
transition: background 0.1s ease;
|
||||
|
||||
white-space: nowrap;
|
||||
@@ -67,8 +68,10 @@ const StyledButton = styled.button<
|
||||
min-width: ${({ size }) => (size === 'small' ? '24px' : '32px')};
|
||||
|
||||
&:hover {
|
||||
background: ${({ theme, disabled }) =>
|
||||
!disabled ? theme.background.transparent.light : 'transparent'};
|
||||
background: ${({ disabled }) =>
|
||||
!disabled
|
||||
? themeCssVariables.background.transparent.light
|
||||
: 'transparent'};
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@@ -76,8 +79,10 @@ const StyledButton = styled.button<
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: ${({ theme, disabled }) =>
|
||||
!disabled ? theme.background.transparent.medium : 'transparent'};
|
||||
background: ${({ disabled }) =>
|
||||
!disabled
|
||||
? themeCssVariables.background.transparent.medium
|
||||
: 'transparent'};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -102,7 +107,7 @@ export const AnimatedLightIconButton = ({
|
||||
onClick,
|
||||
title,
|
||||
}: AnimatedLightIconButtonProps) => {
|
||||
const theme = useTheme();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<StyledButton
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import isPropValid from '@emotion/is-prop-valid';
|
||||
import { css } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type IconComponent } from '@ui/display/icon/types/IconComponent';
|
||||
import { ButtonHotkeys } from '@ui/input/button/components/Button/internal/ButtonHotKeys';
|
||||
import { ButtonIcon } from '@ui/input/button/components/Button/internal/ButtonIcon';
|
||||
import { ButtonSoon } from '@ui/input/button/components/Button/internal/ButtonSoon';
|
||||
import { GRAY_SCALE_LIGHT } from '@ui/theme';
|
||||
import { GRAY_SCALE_LIGHT, themeCssVariables } from '@ui/theme';
|
||||
import { useIsMobile } from '@ui/utilities';
|
||||
import { type ClickOutsideAttributes } from '@ui/utilities/types/ClickOutsideAttributes';
|
||||
import React, { useState } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { ButtonText } from './internal/ButtonText';
|
||||
@@ -42,18 +40,295 @@ export type ButtonProps = {
|
||||
} & Pick<React.ComponentProps<'button'>, 'type'> &
|
||||
ClickOutsideAttributes;
|
||||
|
||||
const StyledButton = styled('button', {
|
||||
shouldForwardProp: (prop) =>
|
||||
!['fullWidth'].includes(prop) && isPropValid(prop),
|
||||
})<
|
||||
type ButtonDynamicStyles = {
|
||||
background: string;
|
||||
borderColor: string;
|
||||
borderWidthOverride: string;
|
||||
boxShadow: string;
|
||||
color: string;
|
||||
hoverBackground: string;
|
||||
activeBackground: string;
|
||||
};
|
||||
|
||||
const computeButtonDynamicStyles = (
|
||||
variant: ButtonVariant,
|
||||
accent: ButtonAccent,
|
||||
inverted: boolean,
|
||||
disabled: boolean,
|
||||
focus: boolean,
|
||||
position: ButtonPosition,
|
||||
): ButtonDynamicStyles => {
|
||||
const result: ButtonDynamicStyles = {
|
||||
background: 'transparent',
|
||||
borderColor: 'transparent',
|
||||
borderWidthOverride: '',
|
||||
boxShadow: 'none',
|
||||
color: themeCssVariables.font.color.secondary,
|
||||
hoverBackground: 'transparent',
|
||||
activeBackground: 'transparent',
|
||||
};
|
||||
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
switch (accent) {
|
||||
case 'default':
|
||||
result.background = !inverted
|
||||
? themeCssVariables.background.secondary
|
||||
: themeCssVariables.background.primary;
|
||||
result.borderColor = !inverted
|
||||
? !disabled && focus
|
||||
? themeCssVariables.color.blue
|
||||
: themeCssVariables.background.transparent.light
|
||||
: themeCssVariables.background.transparent.light;
|
||||
result.borderWidthOverride = '1px 1px 1px 1px';
|
||||
result.boxShadow =
|
||||
!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? themeCssVariables.accent.tertiary
|
||||
: themeCssVariables.background.transparent.medium
|
||||
}`
|
||||
: 'none';
|
||||
result.color = !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.font.color.secondary
|
||||
: themeCssVariables.font.color.extraLight
|
||||
: themeCssVariables.font.color.secondary;
|
||||
if (!disabled) {
|
||||
result.hoverBackground = !inverted
|
||||
? themeCssVariables.background.tertiary
|
||||
: themeCssVariables.background.secondary;
|
||||
result.activeBackground = !inverted
|
||||
? themeCssVariables.background.quaternary
|
||||
: themeCssVariables.background.tertiary;
|
||||
} else {
|
||||
result.hoverBackground = result.background;
|
||||
result.activeBackground = result.background;
|
||||
}
|
||||
break;
|
||||
case 'blue':
|
||||
result.background = !inverted
|
||||
? disabled
|
||||
? themeCssVariables.accent.accent4060
|
||||
: themeCssVariables.color.blue
|
||||
: themeCssVariables.background.primary;
|
||||
result.borderColor = !inverted
|
||||
? focus
|
||||
? themeCssVariables.color.blue
|
||||
: themeCssVariables.background.transparent.light
|
||||
: themeCssVariables.background.transparent.light;
|
||||
result.borderWidthOverride = '1px 1px 1px 1px';
|
||||
result.boxShadow =
|
||||
!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? themeCssVariables.accent.tertiary
|
||||
: themeCssVariables.background.transparent.medium
|
||||
}`
|
||||
: 'none';
|
||||
result.color = !inverted
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
: themeCssVariables.color.blue;
|
||||
if (!disabled) {
|
||||
result.hoverBackground = !inverted
|
||||
? themeCssVariables.color.blue10
|
||||
: themeCssVariables.background.secondary;
|
||||
result.activeBackground = !inverted
|
||||
? themeCssVariables.color.blue12
|
||||
: themeCssVariables.background.tertiary;
|
||||
} else {
|
||||
result.hoverBackground = result.background;
|
||||
result.activeBackground = result.background;
|
||||
}
|
||||
break;
|
||||
case 'danger':
|
||||
result.background = !inverted
|
||||
? themeCssVariables.color.red
|
||||
: themeCssVariables.background.primary;
|
||||
result.borderColor = !inverted
|
||||
? focus
|
||||
? themeCssVariables.color.red
|
||||
: themeCssVariables.background.transparent.light
|
||||
: themeCssVariables.background.transparent.light;
|
||||
result.borderWidthOverride = '1px 1px';
|
||||
result.boxShadow =
|
||||
!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? themeCssVariables.color.red3
|
||||
: themeCssVariables.background.transparent.medium
|
||||
}`
|
||||
: 'none';
|
||||
result.color = !inverted
|
||||
? themeCssVariables.background.primary
|
||||
: themeCssVariables.color.red;
|
||||
if (!disabled) {
|
||||
result.hoverBackground = !inverted
|
||||
? themeCssVariables.color.red8
|
||||
: themeCssVariables.background.secondary;
|
||||
result.activeBackground = !inverted
|
||||
? themeCssVariables.color.red10
|
||||
: themeCssVariables.background.tertiary;
|
||||
} else {
|
||||
result.hoverBackground = result.background;
|
||||
result.activeBackground = result.background;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'secondary':
|
||||
case 'tertiary':
|
||||
switch (accent) {
|
||||
case 'default':
|
||||
result.borderColor = !inverted
|
||||
? variant === 'secondary'
|
||||
? !disabled && focus
|
||||
? themeCssVariables.color.blue
|
||||
: themeCssVariables.background.transparent.medium
|
||||
: focus
|
||||
? themeCssVariables.color.blue
|
||||
: 'transparent'
|
||||
: variant === 'secondary'
|
||||
? focus || disabled
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
: themeCssVariables.background.transparent.primary
|
||||
: focus
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
: 'transparent';
|
||||
result.borderWidthOverride = '1px 1px 1px 1px';
|
||||
result.boxShadow =
|
||||
!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? themeCssVariables.accent.tertiary
|
||||
: themeCssVariables.background.transparent.medium
|
||||
}`
|
||||
: 'none';
|
||||
result.color = !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.font.color.secondary
|
||||
: themeCssVariables.font.color.extraLight
|
||||
: themeCssVariables.font.color.inverted;
|
||||
result.hoverBackground = !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.background.transparent.light
|
||||
: 'transparent'
|
||||
: themeCssVariables.background.transparent.light;
|
||||
result.activeBackground = !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.background.transparent.light
|
||||
: 'transparent'
|
||||
: themeCssVariables.background.transparent.medium;
|
||||
break;
|
||||
case 'blue':
|
||||
result.borderColor = !inverted
|
||||
? variant === 'secondary'
|
||||
? focus
|
||||
? themeCssVariables.color.blue
|
||||
: themeCssVariables.accent.primary
|
||||
: focus
|
||||
? themeCssVariables.color.blue
|
||||
: 'transparent'
|
||||
: variant === 'secondary'
|
||||
? focus || disabled
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
: themeCssVariables.background.transparent.primary
|
||||
: focus
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
: 'transparent';
|
||||
result.borderWidthOverride = '1px 1px 1px 1px';
|
||||
result.boxShadow =
|
||||
!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? themeCssVariables.accent.tertiary
|
||||
: themeCssVariables.background.transparent.medium
|
||||
}`
|
||||
: 'none';
|
||||
result.color = !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.color.blue
|
||||
: themeCssVariables.accent.accent4060
|
||||
: themeCssVariables.font.color.inverted;
|
||||
result.hoverBackground = !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.accent.tertiary
|
||||
: 'transparent'
|
||||
: themeCssVariables.background.transparent.light;
|
||||
result.activeBackground = !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.accent.secondary
|
||||
: 'transparent'
|
||||
: themeCssVariables.background.transparent.medium;
|
||||
break;
|
||||
case 'danger':
|
||||
result.borderColor = !inverted
|
||||
? variant === 'secondary'
|
||||
? focus
|
||||
? themeCssVariables.color.red
|
||||
: themeCssVariables.border.color.danger
|
||||
: focus
|
||||
? themeCssVariables.color.red
|
||||
: 'transparent'
|
||||
: variant === 'secondary'
|
||||
? focus || disabled
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
: themeCssVariables.background.transparent.primary
|
||||
: focus
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
: 'transparent';
|
||||
result.borderWidthOverride = '1px 1px 1px 1px';
|
||||
result.boxShadow =
|
||||
!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? themeCssVariables.color.red3
|
||||
: themeCssVariables.background.transparent.medium
|
||||
}`
|
||||
: 'none';
|
||||
result.color = !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.font.color.danger
|
||||
: themeCssVariables.color.red5
|
||||
: themeCssVariables.font.color.inverted;
|
||||
result.hoverBackground = !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.background.danger
|
||||
: 'transparent'
|
||||
: themeCssVariables.background.transparent.light;
|
||||
result.activeBackground = !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.background.danger
|
||||
: 'transparent'
|
||||
: themeCssVariables.background.transparent.medium;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.borderWidthOverride !== '' && position !== 'standalone') {
|
||||
switch (position) {
|
||||
case 'left':
|
||||
result.borderWidthOverride = '1px 0px 1px 1px';
|
||||
break;
|
||||
case 'middle':
|
||||
result.borderWidthOverride = '1px 0px 1px 0px';
|
||||
break;
|
||||
case 'right':
|
||||
result.borderWidthOverride = '1px 1px 1px 0px';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const StyledButton = styled.button<
|
||||
Pick<
|
||||
ButtonProps,
|
||||
| 'fullWidth'
|
||||
| 'variant'
|
||||
| 'inverted'
|
||||
| 'size'
|
||||
| 'position'
|
||||
| 'accent'
|
||||
| 'focus'
|
||||
| 'justify'
|
||||
| 'to'
|
||||
@@ -62,300 +337,37 @@ const StyledButton = styled('button', {
|
||||
> & { hasIcon: boolean }
|
||||
>`
|
||||
align-items: center;
|
||||
${({ theme, variant, inverted, accent, disabled, focus }) => {
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
switch (accent) {
|
||||
case 'default':
|
||||
return css`
|
||||
background: ${!inverted
|
||||
? theme.background.secondary
|
||||
: theme.background.primary};
|
||||
border-color: ${!inverted
|
||||
? !disabled && focus
|
||||
? theme.color.blue
|
||||
: theme.background.transparent.light
|
||||
: theme.background.transparent.light};
|
||||
border-width: 1px 1px 1px 1px !important;
|
||||
box-shadow: ${!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? theme.accent.tertiary
|
||||
: theme.background.transparent.medium
|
||||
}`
|
||||
: 'none'};
|
||||
color: ${!inverted
|
||||
? !disabled
|
||||
? theme.font.color.secondary
|
||||
: theme.font.color.extraLight
|
||||
: theme.font.color.secondary};
|
||||
${disabled
|
||||
? ''
|
||||
: css`
|
||||
&:hover {
|
||||
background: ${!inverted
|
||||
? theme.background.tertiary
|
||||
: theme.background.secondary};
|
||||
}
|
||||
&:active {
|
||||
background: ${!inverted
|
||||
? theme.background.quaternary
|
||||
: theme.background.tertiary};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
case 'blue':
|
||||
return css`
|
||||
background: ${!inverted
|
||||
? disabled
|
||||
? theme.accent.accent4060
|
||||
: theme.color.blue
|
||||
: theme.background.primary};
|
||||
border-color: ${!inverted
|
||||
? focus
|
||||
? theme.color.blue
|
||||
: theme.background.transparent.light
|
||||
: theme.background.transparent.light};
|
||||
border-width: 1px 1px 1px 1px !important;
|
||||
box-shadow: ${!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? theme.accent.tertiary
|
||||
: theme.background.transparent.medium
|
||||
}`
|
||||
: 'none'};
|
||||
color: ${!inverted ? GRAY_SCALE_LIGHT.gray1 : theme.color.blue};
|
||||
${disabled
|
||||
? ''
|
||||
: css`
|
||||
&:hover {
|
||||
background: ${!inverted
|
||||
? theme.color.blue10
|
||||
: theme.background.secondary};
|
||||
}
|
||||
&:active {
|
||||
background: ${!inverted
|
||||
? theme.color.blue12
|
||||
: theme.background.tertiary};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
case 'danger':
|
||||
return css`
|
||||
background: ${!inverted
|
||||
? theme.color.red
|
||||
: theme.background.primary};
|
||||
border-color: ${!inverted
|
||||
? focus
|
||||
? theme.color.red
|
||||
: theme.background.transparent.light
|
||||
: theme.background.transparent.light};
|
||||
border-width: 1px 1px !important;
|
||||
box-shadow: ${!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? theme.color.red3
|
||||
: theme.background.transparent.medium
|
||||
}`
|
||||
: 'none'};
|
||||
color: ${!inverted ? theme.background.primary : theme.color.red};
|
||||
${disabled
|
||||
? ''
|
||||
: css`
|
||||
&:hover {
|
||||
background: ${!inverted
|
||||
? theme.color.red8
|
||||
: theme.background.secondary};
|
||||
}
|
||||
&:active {
|
||||
background: ${!inverted
|
||||
? theme.color.red10
|
||||
: theme.background.tertiary};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
}
|
||||
break;
|
||||
case 'secondary':
|
||||
case 'tertiary':
|
||||
switch (accent) {
|
||||
case 'default':
|
||||
return css`
|
||||
background: transparent;
|
||||
border-color: ${!inverted
|
||||
? variant === 'secondary'
|
||||
? !disabled && focus
|
||||
? theme.color.blue
|
||||
: theme.background.transparent.medium
|
||||
: focus
|
||||
? theme.color.blue
|
||||
: 'transparent'
|
||||
: variant === 'secondary'
|
||||
? focus || disabled
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
: theme.background.transparent.primary
|
||||
: focus
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
: 'transparent'};
|
||||
border-width: 1px 1px 1px 1px !important;
|
||||
box-shadow: ${!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? theme.accent.tertiary
|
||||
: theme.background.transparent.medium
|
||||
}`
|
||||
: 'none'};
|
||||
color: ${!inverted
|
||||
? !disabled
|
||||
? theme.font.color.secondary
|
||||
: theme.font.color.extraLight
|
||||
: theme.font.color.inverted};
|
||||
&:hover {
|
||||
background: ${!inverted
|
||||
? !disabled
|
||||
? theme.background.transparent.light
|
||||
: 'transparent'
|
||||
: theme.background.transparent.light};
|
||||
}
|
||||
&:active {
|
||||
background: ${!inverted
|
||||
? !disabled
|
||||
? theme.background.transparent.light
|
||||
: 'transparent'
|
||||
: theme.background.transparent.medium};
|
||||
}
|
||||
`;
|
||||
case 'blue':
|
||||
return css`
|
||||
background: transparent;
|
||||
border-color: ${!inverted
|
||||
? variant === 'secondary'
|
||||
? focus
|
||||
? theme.color.blue
|
||||
: theme.accent.primary
|
||||
: focus
|
||||
? theme.color.blue
|
||||
: 'transparent'
|
||||
: variant === 'secondary'
|
||||
? focus || disabled
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
: theme.background.transparent.primary
|
||||
: focus
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
: 'transparent'};
|
||||
border-width: 1px 1px 1px 1px !important;
|
||||
box-shadow: ${!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? theme.accent.tertiary
|
||||
: theme.background.transparent.medium
|
||||
}`
|
||||
: 'none'};
|
||||
color: ${!inverted
|
||||
? !disabled
|
||||
? theme.color.blue
|
||||
: theme.accent.accent4060
|
||||
: theme.font.color.inverted};
|
||||
&:hover {
|
||||
background: ${!inverted
|
||||
? !disabled
|
||||
? theme.accent.tertiary
|
||||
: 'transparent'
|
||||
: theme.background.transparent.light};
|
||||
}
|
||||
&:active {
|
||||
background: ${!inverted
|
||||
? !disabled
|
||||
? theme.accent.secondary
|
||||
: 'transparent'
|
||||
: theme.background.transparent.medium};
|
||||
}
|
||||
`;
|
||||
case 'danger':
|
||||
return css`
|
||||
background: transparent;
|
||||
border-color: ${!inverted
|
||||
? variant === 'secondary'
|
||||
? focus
|
||||
? theme.color.red
|
||||
: theme.border.color.danger
|
||||
: focus
|
||||
? theme.color.red
|
||||
: 'transparent'
|
||||
: variant === 'secondary'
|
||||
? focus || disabled
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
: theme.background.transparent.primary
|
||||
: focus
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
: 'transparent'};
|
||||
border-width: 1px 1px 1px 1px !important;
|
||||
box-shadow: ${!disabled && focus
|
||||
? `0 0 0 3px ${
|
||||
!inverted
|
||||
? theme.color.red3
|
||||
: theme.background.transparent.medium
|
||||
}`
|
||||
: 'none'};
|
||||
color: ${!inverted
|
||||
? !disabled
|
||||
? theme.font.color.danger
|
||||
: theme.color.red5
|
||||
: theme.font.color.inverted};
|
||||
&:hover {
|
||||
background: ${!inverted
|
||||
? !disabled
|
||||
? theme.background.danger
|
||||
: 'transparent'
|
||||
: theme.background.transparent.light};
|
||||
}
|
||||
&:active {
|
||||
background: ${!inverted
|
||||
? !disabled
|
||||
? theme.background.danger
|
||||
: 'transparent'
|
||||
: theme.background.transparent.medium};
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
}}
|
||||
background: var(--btn-bg);
|
||||
border-color: var(--btn-border-color);
|
||||
border-width: var(--btn-border-width);
|
||||
box-shadow: var(--btn-box-shadow);
|
||||
color: var(--btn-color);
|
||||
|
||||
text-decoration: none;
|
||||
border-radius: ${({ position, theme }) => {
|
||||
border-radius: ${({ position }) => {
|
||||
switch (position) {
|
||||
case 'left':
|
||||
return `${theme.border.radius.sm} 0px 0px ${theme.border.radius.sm}`;
|
||||
return `${themeCssVariables.border.radius.sm} 0px 0px ${themeCssVariables.border.radius.sm}`;
|
||||
case 'right':
|
||||
return `0px ${theme.border.radius.sm} ${theme.border.radius.sm} 0px`;
|
||||
return `0px ${themeCssVariables.border.radius.sm} ${themeCssVariables.border.radius.sm} 0px`;
|
||||
case 'middle':
|
||||
return '0px';
|
||||
case 'standalone':
|
||||
return theme.border.radius.sm;
|
||||
return themeCssVariables.border.radius.sm;
|
||||
}
|
||||
return '';
|
||||
}};
|
||||
border-style: solid;
|
||||
border-width: ${({ variant, position }) => {
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
case 'secondary':
|
||||
return position === 'middle' ? '1px 0px' : '1px';
|
||||
case 'tertiary':
|
||||
return '0';
|
||||
}
|
||||
}};
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
font-family: ${themeCssVariables.font.family};
|
||||
font-weight: 500;
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
font-size: ${themeCssVariables.font.size.md};
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
height: ${({ size }) => (size === 'small' ? '24px' : '32px')};
|
||||
justify-content: ${({ justify }) => justify};
|
||||
padding: ${({ theme }) => {
|
||||
return `0 ${theme.spacing(2)} 0 ${theme.spacing(2)}`;
|
||||
}};
|
||||
justify-content: ${({ justify }) => justify ?? ''};
|
||||
padding: 0 ${themeCssVariables.spacing[2]} 0 ${themeCssVariables.spacing[2]};
|
||||
box-sizing: border-box;
|
||||
|
||||
transition: background 0.1s ease;
|
||||
@@ -364,67 +376,78 @@ const StyledButton = styled('button', {
|
||||
|
||||
width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')};
|
||||
|
||||
&:hover {
|
||||
background: var(--btn-hover-bg);
|
||||
}
|
||||
&:active {
|
||||
background: var(--btn-active-bg);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledButtonWrapper = styled.div<
|
||||
Pick<
|
||||
ButtonProps,
|
||||
'isLoading' | 'variant' | 'accent' | 'inverted' | 'disabled' | 'fullWidth'
|
||||
>
|
||||
Pick<ButtonProps, 'isLoading' | 'fullWidth'>
|
||||
>`
|
||||
${({ theme, variant, accent, inverted, disabled }) => css`
|
||||
--tw-button-color: ${(() => {
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
switch (accent) {
|
||||
case 'default':
|
||||
return !inverted
|
||||
? !disabled
|
||||
? theme.font.color.secondary
|
||||
: theme.font.color.extraLight
|
||||
: theme.font.color.secondary;
|
||||
case 'blue':
|
||||
return !inverted ? GRAY_SCALE_LIGHT.gray1 : theme.color.blue;
|
||||
case 'danger':
|
||||
return !inverted ? theme.background.primary : theme.color.red;
|
||||
}
|
||||
break;
|
||||
case 'secondary':
|
||||
case 'tertiary':
|
||||
switch (accent) {
|
||||
case 'default':
|
||||
return !inverted
|
||||
? !disabled
|
||||
? theme.font.color.secondary
|
||||
: theme.font.color.extraLight
|
||||
: theme.font.color.inverted;
|
||||
case 'blue':
|
||||
return !inverted
|
||||
? !disabled
|
||||
? theme.color.blue
|
||||
: theme.accent.accent4060
|
||||
: theme.font.color.inverted;
|
||||
case 'danger':
|
||||
return !inverted
|
||||
? theme.font.color.danger
|
||||
: theme.font.color.inverted;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return theme.font.color.secondary; // Valeur par défaut
|
||||
})()};
|
||||
`}
|
||||
|
||||
max-width: ${({ isLoading, theme }) =>
|
||||
isLoading ? `calc(100% - ${theme.spacing(8)})` : 'none'};
|
||||
max-width: ${({ isLoading }) =>
|
||||
isLoading ? `calc(100% - ${themeCssVariables.spacing[8]})` : 'none'};
|
||||
|
||||
position: relative;
|
||||
width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')};
|
||||
`;
|
||||
|
||||
const computeButtonWrapperColor = (
|
||||
variant: ButtonVariant,
|
||||
accent: ButtonAccent,
|
||||
inverted: boolean,
|
||||
disabled: boolean,
|
||||
): string => {
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
switch (accent) {
|
||||
case 'default':
|
||||
return !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.font.color.secondary
|
||||
: themeCssVariables.font.color.extraLight
|
||||
: themeCssVariables.font.color.secondary;
|
||||
case 'blue':
|
||||
return !inverted
|
||||
? GRAY_SCALE_LIGHT.gray1
|
||||
: themeCssVariables.color.blue;
|
||||
case 'danger':
|
||||
return !inverted
|
||||
? themeCssVariables.background.primary
|
||||
: themeCssVariables.color.red;
|
||||
}
|
||||
break;
|
||||
case 'secondary':
|
||||
case 'tertiary':
|
||||
switch (accent) {
|
||||
case 'default':
|
||||
return !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.font.color.secondary
|
||||
: themeCssVariables.font.color.extraLight
|
||||
: themeCssVariables.font.color.inverted;
|
||||
case 'blue':
|
||||
return !inverted
|
||||
? !disabled
|
||||
? themeCssVariables.color.blue
|
||||
: themeCssVariables.accent.accent4060
|
||||
: themeCssVariables.font.color.inverted;
|
||||
case 'danger':
|
||||
return !inverted
|
||||
? themeCssVariables.font.color.danger
|
||||
: themeCssVariables.font.color.inverted;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return themeCssVariables.font.color.secondary;
|
||||
};
|
||||
|
||||
export const Button = ({
|
||||
className,
|
||||
Icon,
|
||||
@@ -453,25 +476,47 @@ export const Button = ({
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const [isFocused, setIsFocused] = useState(propFocus);
|
||||
const isDisabled = soon || disabled;
|
||||
|
||||
const dynamicStyles = useMemo(() => {
|
||||
const s = computeButtonDynamicStyles(
|
||||
variant,
|
||||
accent,
|
||||
inverted,
|
||||
isDisabled,
|
||||
isFocused,
|
||||
position,
|
||||
);
|
||||
return {
|
||||
'--btn-bg': s.background,
|
||||
'--btn-border-color': s.borderColor,
|
||||
'--btn-border-width': s.borderWidthOverride || undefined,
|
||||
'--btn-box-shadow': s.boxShadow,
|
||||
'--btn-color': s.color,
|
||||
'--btn-hover-bg': s.hoverBackground,
|
||||
'--btn-active-bg': s.activeBackground,
|
||||
'--tw-button-color': computeButtonWrapperColor(
|
||||
variant,
|
||||
accent,
|
||||
inverted,
|
||||
isDisabled,
|
||||
),
|
||||
} as React.CSSProperties;
|
||||
}, [variant, accent, inverted, isDisabled, isFocused, position]);
|
||||
|
||||
return (
|
||||
<StyledButtonWrapper
|
||||
isLoading={!!isLoading}
|
||||
variant={variant}
|
||||
accent={accent}
|
||||
inverted={inverted}
|
||||
disabled={soon || disabled}
|
||||
fullWidth={fullWidth}
|
||||
style={dynamicStyles}
|
||||
>
|
||||
<StyledButton
|
||||
fullWidth={fullWidth}
|
||||
variant={variant}
|
||||
inverted={inverted}
|
||||
position={position}
|
||||
disabled={soon || disabled}
|
||||
disabled={isDisabled}
|
||||
hasIcon={!!Icon}
|
||||
focus={isFocused}
|
||||
justify={justify}
|
||||
accent={accent}
|
||||
className={className}
|
||||
onClick={onClick}
|
||||
to={to}
|
||||
@@ -486,6 +531,7 @@ export const Button = ({
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
size={size}
|
||||
style={dynamicStyles}
|
||||
>
|
||||
{(isLoading || Icon) && (
|
||||
<ButtonIcon Icon={Icon} isLoading={!!isLoading} />
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import {
|
||||
type ButtonAccent,
|
||||
type ButtonSize,
|
||||
type ButtonVariant,
|
||||
} from '@ui/input';
|
||||
import { themeCssVariables } from '@ui/theme';
|
||||
import { getOsShortcutSeparator } from '@ui/utilities';
|
||||
|
||||
const StyledSeparator = styled.div<{
|
||||
buttonSize: ButtonSize;
|
||||
accent: ButtonAccent;
|
||||
}>`
|
||||
background: ${({ theme, accent }) => {
|
||||
background: ${({ accent }) => {
|
||||
switch (accent) {
|
||||
case 'blue':
|
||||
return theme.buttons.secondaryTextColor;
|
||||
return themeCssVariables.buttons.secondaryTextColor;
|
||||
case 'danger':
|
||||
return theme.border.color.danger;
|
||||
return themeCssVariables.border.color.danger;
|
||||
default:
|
||||
return theme.font.color.light;
|
||||
return themeCssVariables.font.color.light;
|
||||
}
|
||||
}};
|
||||
height: ${({ theme, buttonSize }) =>
|
||||
theme.spacing(buttonSize === 'small' ? 2 : 4)};
|
||||
height: ${({ buttonSize }) =>
|
||||
buttonSize === 'small'
|
||||
? themeCssVariables.spacing[2]
|
||||
: themeCssVariables.spacing[4]};
|
||||
margin: 0;
|
||||
width: 1px;
|
||||
`;
|
||||
@@ -30,19 +34,19 @@ const StyledShortcutLabel = styled.div<{
|
||||
variant: ButtonVariant;
|
||||
accent: ButtonAccent;
|
||||
}>`
|
||||
color: ${({ theme, variant, accent }) => {
|
||||
color: ${({ variant, accent }) => {
|
||||
switch (accent) {
|
||||
case 'blue':
|
||||
return theme.buttons.secondaryTextColor;
|
||||
return themeCssVariables.buttons.secondaryTextColor;
|
||||
case 'danger':
|
||||
return variant === 'primary'
|
||||
? theme.border.color.danger
|
||||
: theme.color.red8;
|
||||
? themeCssVariables.border.color.danger
|
||||
: themeCssVariables.color.red8;
|
||||
default:
|
||||
return theme.font.color.light;
|
||||
return themeCssVariables.font.color.light;
|
||||
}
|
||||
}};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
font-weight: ${themeCssVariables.font.weight.medium};
|
||||
`;
|
||||
|
||||
export const ButtonHotkeys = ({
|
||||
@@ -55,11 +59,13 @@ export const ButtonHotkeys = ({
|
||||
accent: ButtonAccent;
|
||||
variant: ButtonVariant;
|
||||
hotkeys: string[];
|
||||
}) => (
|
||||
<>
|
||||
<StyledSeparator buttonSize={size} accent={accent} />
|
||||
<StyledShortcutLabel variant={variant} accent={accent}>
|
||||
{hotkeys.join(getOsShortcutSeparator())}
|
||||
</StyledShortcutLabel>
|
||||
</>
|
||||
);
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<StyledSeparator buttonSize={size} accent={accent} />
|
||||
<StyledShortcutLabel variant={variant} accent={accent}>
|
||||
{hotkeys.join(getOsShortcutSeparator())}
|
||||
</StyledShortcutLabel>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type IconComponent } from '@ui/display';
|
||||
import { Loader } from '@ui/feedback';
|
||||
import { baseTransitionTiming } from '@ui/input/button/components/Button/constant';
|
||||
import { ThemeContext, themeCssVariables } from '@ui/theme';
|
||||
import { useContext } from 'react';
|
||||
|
||||
const StyledIcon = styled.div<{
|
||||
isLoading: boolean;
|
||||
}>`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: calc(100% - ${({ theme }) => theme.spacing(4)});
|
||||
height: calc(100% - ${themeCssVariables.spacing[4]});
|
||||
color: var(--tw-button-color);
|
||||
|
||||
opacity: ${({ isLoading }) => (isLoading ? 0 : 1)};
|
||||
@@ -26,13 +27,13 @@ const StyledIconWrapper = styled.div`
|
||||
`;
|
||||
|
||||
const StyledLoader = styled.div`
|
||||
left: ${({ theme }) => theme.spacing(2)};
|
||||
left: ${themeCssVariables.spacing[2]};
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
|
||||
transition: opacity ${baseTransitionTiming / 2}ms ease;
|
||||
transition-delay: ${baseTransitionTiming / 2}ms;
|
||||
width: ${({ theme }) => theme.spacing(6)};
|
||||
width: ${themeCssVariables.spacing[6]};
|
||||
`;
|
||||
|
||||
export const ButtonIcon = ({
|
||||
@@ -42,7 +43,7 @@ export const ButtonIcon = ({
|
||||
Icon?: IconComponent;
|
||||
isLoading?: boolean;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
return (
|
||||
<StyledIconWrapper>
|
||||
{isLoading && (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
import { Pill } from '@ui/components';
|
||||
|
||||
const StyledSoonPill = styled(Pill)`
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import { baseTransitionTiming } from '@ui/input/button/components/Button/constant';
|
||||
import { themeCssVariables } from '@ui/theme';
|
||||
|
||||
const StyledEllipsis = styled.div<{ isLoading?: boolean }>`
|
||||
right: 0;
|
||||
clip-path: ${({ theme, isLoading }) =>
|
||||
isLoading ? `inset(0 0 0 0)` : `inset(0 0 0 ${theme.spacing(6)})`};
|
||||
clip-path: ${({ isLoading }) =>
|
||||
isLoading
|
||||
? `inset(0 0 0 0)`
|
||||
: `inset(0 0 0 ${themeCssVariables.spacing[6]})`};
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
|
||||
@@ -19,17 +23,20 @@ const StyledTextWrapper = styled.div`
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const StyledText = styled.div<{ isLoading?: boolean; hasIcon: boolean }>`
|
||||
clip-path: ${({ isLoading, theme, hasIcon }) =>
|
||||
const StyledText = styled.div<{
|
||||
isLoading?: boolean;
|
||||
hasIcon: boolean;
|
||||
}>`
|
||||
clip-path: ${({ isLoading, hasIcon }) =>
|
||||
isLoading
|
||||
? ` inset(0 ${!hasIcon ? theme.spacing(12) : theme.spacing(6)} 0 0)`
|
||||
? ` inset(0 ${!hasIcon ? themeCssVariables.spacing[12] : themeCssVariables.spacing[6]} 0 0)`
|
||||
: ' inset(0 0 0 0)'};
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
transform: ${({ theme, isLoading, hasIcon }) =>
|
||||
transform: ${({ isLoading, hasIcon }) =>
|
||||
isLoading
|
||||
? `translateX(${!hasIcon ? theme.spacing(7) : theme.spacing(3)})`
|
||||
? `translateX(${!hasIcon ? themeCssVariables.spacing[7] : themeCssVariables.spacing[3]})`
|
||||
: 'none'};
|
||||
|
||||
transition:
|
||||
@@ -49,11 +56,13 @@ export const ButtonText = ({
|
||||
isLoading?: boolean;
|
||||
hasIcon: boolean;
|
||||
title?: string;
|
||||
}) => (
|
||||
<StyledTextWrapper>
|
||||
<StyledText isLoading={isLoading} hasIcon={hasIcon}>
|
||||
{title}
|
||||
</StyledText>
|
||||
<StyledEllipsis isLoading={isLoading}>...</StyledEllipsis>
|
||||
</StyledTextWrapper>
|
||||
);
|
||||
}) => {
|
||||
return (
|
||||
<StyledTextWrapper>
|
||||
<StyledText isLoading={isLoading} hasIcon={hasIcon}>
|
||||
{title}
|
||||
</StyledText>
|
||||
<StyledEllipsis isLoading={isLoading}>...</StyledEllipsis>
|
||||
</StyledTextWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
import React, { type ReactNode } from 'react';
|
||||
|
||||
import { type ButtonPosition, type ButtonProps } from './Button/Button';
|
||||
import { themeCssVariables } from '@ui/theme';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const StyledButtonGroupContainer = styled.div`
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
border-radius: ${themeCssVariables.border.radius.md};
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
@@ -22,36 +23,38 @@ export const ButtonGroup = ({
|
||||
variant,
|
||||
size,
|
||||
accent,
|
||||
}: ButtonGroupProps) => (
|
||||
<StyledButtonGroupContainer className={className}>
|
||||
{React.Children.map(children, (child, index) => {
|
||||
if (!React.isValidElement(child)) return null;
|
||||
}: ButtonGroupProps) => {
|
||||
return (
|
||||
<StyledButtonGroupContainer className={className}>
|
||||
{React.Children.map(children, (child, index) => {
|
||||
if (!React.isValidElement(child)) return null;
|
||||
|
||||
let position: ButtonPosition;
|
||||
let position: ButtonPosition;
|
||||
|
||||
if (index === 0) {
|
||||
position = 'left';
|
||||
} else if (index === children.length - 1) {
|
||||
position = 'right';
|
||||
} else {
|
||||
position = 'middle';
|
||||
}
|
||||
if (index === 0) {
|
||||
position = 'left';
|
||||
} else if (index === children.length - 1) {
|
||||
position = 'right';
|
||||
} else {
|
||||
position = 'middle';
|
||||
}
|
||||
|
||||
const additionalProps: any = { position, variant, accent, size };
|
||||
const additionalProps: any = { position, variant, accent, size };
|
||||
|
||||
if (isDefined(variant)) {
|
||||
additionalProps.variant = variant;
|
||||
}
|
||||
if (isDefined(variant)) {
|
||||
additionalProps.variant = variant;
|
||||
}
|
||||
|
||||
if (isDefined(accent)) {
|
||||
additionalProps.variant = variant;
|
||||
}
|
||||
if (isDefined(accent)) {
|
||||
additionalProps.accent = accent;
|
||||
}
|
||||
|
||||
if (isDefined(size)) {
|
||||
additionalProps.size = size;
|
||||
}
|
||||
if (isDefined(size)) {
|
||||
additionalProps.size = size;
|
||||
}
|
||||
|
||||
return React.cloneElement(child, additionalProps);
|
||||
})}
|
||||
</StyledButtonGroupContainer>
|
||||
);
|
||||
return React.cloneElement(child, additionalProps);
|
||||
})}
|
||||
</StyledButtonGroupContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { css } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
import { ColorSample, type ColorSampleProps } from '@ui/display';
|
||||
import { themeCssVariables } from '@ui/theme';
|
||||
import {
|
||||
LightIconButton,
|
||||
type LightIconButtonProps,
|
||||
@@ -11,30 +11,36 @@ type ColorPickerButtonProps = Pick<ColorSampleProps, 'colorName'> &
|
||||
isSelected?: boolean;
|
||||
};
|
||||
|
||||
const StyledButton = styled(LightIconButton)<{
|
||||
const StyledButtonWrapper = styled.div<{
|
||||
isSelected?: boolean;
|
||||
}>`
|
||||
${({ isSelected, theme }) =>
|
||||
isSelected
|
||||
? css`
|
||||
background-color: ${theme.background.transparent.medium};
|
||||
button {
|
||||
${({ isSelected }) =>
|
||||
isSelected
|
||||
? `background-color: ${themeCssVariables.background.transparent.medium};`
|
||||
: ''}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: ${theme.background.transparent.medium};
|
||||
}
|
||||
`
|
||||
: ''}
|
||||
button:hover {
|
||||
${({ isSelected }) =>
|
||||
isSelected
|
||||
? `background-color: ${themeCssVariables.background.transparent.medium};`
|
||||
: ''}
|
||||
}
|
||||
`;
|
||||
|
||||
export const ColorPickerButton = ({
|
||||
colorName,
|
||||
isSelected,
|
||||
onClick,
|
||||
}: ColorPickerButtonProps) => (
|
||||
<StyledButton
|
||||
size="medium"
|
||||
isSelected={isSelected}
|
||||
Icon={() => <ColorSample colorName={colorName} />}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
}: ColorPickerButtonProps) => {
|
||||
return (
|
||||
<StyledButtonWrapper isSelected={isSelected}>
|
||||
<LightIconButton
|
||||
size="medium"
|
||||
Icon={() => <ColorSample colorName={colorName} />}
|
||||
onClick={onClick}
|
||||
/>
|
||||
</StyledButtonWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import isPropValid from '@emotion/is-prop-valid';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type IconComponent } from '@ui/display';
|
||||
import { ThemeContext, themeCssVariables } from '@ui/theme';
|
||||
import { useContext } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export type FloatingButtonSize = 'small' | 'medium';
|
||||
@@ -20,11 +20,7 @@ export type FloatingButtonProps = {
|
||||
to?: string;
|
||||
};
|
||||
|
||||
const StyledButton = styled('button', {
|
||||
shouldForwardProp: (prop) =>
|
||||
!['applyBlur', 'applyShadow', 'focus', 'position', 'size'].includes(prop) &&
|
||||
isPropValid(prop),
|
||||
})<
|
||||
const StyledButton = styled.button<
|
||||
Pick<
|
||||
FloatingButtonProps,
|
||||
| 'size'
|
||||
@@ -38,62 +34,65 @@ const StyledButton = styled('button', {
|
||||
>`
|
||||
align-items: center;
|
||||
backdrop-filter: ${({ applyBlur }) => (applyBlur ? 'blur(20px)' : 'none')};
|
||||
background: ${({ theme }) => theme.background.primary};
|
||||
background: ${themeCssVariables.background.primary};
|
||||
|
||||
border: ${({ focus, theme }) =>
|
||||
focus ? `1px solid ${theme.color.blue}` : 'none'};
|
||||
border-radius: ${({ position, theme }) => {
|
||||
border: ${({ focus }) =>
|
||||
focus ? `1px solid ${themeCssVariables.color.blue}` : 'none'};
|
||||
border-radius: ${({ position }) => {
|
||||
switch (position) {
|
||||
case 'left':
|
||||
return `${theme.border.radius.sm} 0px 0px ${theme.border.radius.sm}`;
|
||||
return `${themeCssVariables.border.radius.sm} 0px 0px ${themeCssVariables.border.radius.sm}`;
|
||||
case 'right':
|
||||
return `0px ${theme.border.radius.sm} ${theme.border.radius.sm} 0px`;
|
||||
return `0px ${themeCssVariables.border.radius.sm} ${themeCssVariables.border.radius.sm} 0px`;
|
||||
case 'middle':
|
||||
return '0px';
|
||||
case 'standalone':
|
||||
return theme.border.radius.sm;
|
||||
return themeCssVariables.border.radius.sm;
|
||||
}
|
||||
return '';
|
||||
}};
|
||||
box-shadow: ${({ theme, applyShadow, focus }) =>
|
||||
box-shadow: ${({ applyShadow, focus }) =>
|
||||
applyShadow
|
||||
? `0px 2px 4px 0px ${
|
||||
theme.background.transparent.light
|
||||
}, 0px 0px 4px 0px ${theme.background.transparent.medium}${
|
||||
focus ? `,0 0 0 3px ${theme.color.blue3}` : ''
|
||||
themeCssVariables.background.transparent.light
|
||||
}, 0px 0px 4px 0px ${themeCssVariables.background.transparent.medium}${
|
||||
focus ? `,0 0 0 3px ${themeCssVariables.color.blue3}` : ''
|
||||
}`
|
||||
: focus
|
||||
? `0 0 0 3px ${theme.color.blue3}`
|
||||
? `0 0 0 3px ${themeCssVariables.color.blue3}`
|
||||
: 'none'};
|
||||
color: ${({ theme, disabled, focus }) => {
|
||||
color: ${({ disabled, focus }) => {
|
||||
return !disabled
|
||||
? focus
|
||||
? theme.color.blue
|
||||
: theme.font.color.secondary
|
||||
: theme.font.color.extraLight;
|
||||
? themeCssVariables.color.blue
|
||||
: themeCssVariables.font.color.secondary
|
||||
: themeCssVariables.font.color.extraLight;
|
||||
}};
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||
display: flex;
|
||||
|
||||
flex-direction: row;
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
font-family: ${themeCssVariables.font.family};
|
||||
font-weight: ${themeCssVariables.font.weight.regular};
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
height: ${({ size }) => (size === 'small' ? '24px' : '32px')};
|
||||
padding: ${({ theme }) => {
|
||||
return `0 ${theme.spacing(2)}`;
|
||||
}};
|
||||
padding: 0 ${themeCssVariables.spacing[2]};
|
||||
transition: background 0.1s ease;
|
||||
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
background: ${({ theme, disabled }) =>
|
||||
!disabled ? theme.background.transparent.lighter : 'transparent'};
|
||||
background: ${({ disabled }) =>
|
||||
!disabled
|
||||
? themeCssVariables.background.transparent.lighter
|
||||
: 'transparent'};
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: ${({ theme, disabled }) =>
|
||||
!disabled ? theme.background.transparent.medium : 'transparent'};
|
||||
background: ${({ disabled }) =>
|
||||
!disabled
|
||||
? themeCssVariables.background.transparent.medium
|
||||
: 'transparent'};
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@@ -114,7 +113,7 @@ export const FloatingButton = ({
|
||||
focus = false,
|
||||
to,
|
||||
}: FloatingButtonProps) => {
|
||||
const theme = useTheme();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
return (
|
||||
<StyledButton
|
||||
disabled={disabled}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
type FloatingButtonPosition,
|
||||
type FloatingButtonProps,
|
||||
} from './FloatingButton';
|
||||
import { themeCssVariables } from '@ui/theme';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const StyledFloatingButtonGroupContainer = styled.div`
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
box-shadow: ${({ theme }) =>
|
||||
`0px 2px 4px 0px ${theme.background.transparent.light}, 0px 0px 4px 0px ${theme.background.transparent.medium}`};
|
||||
border-radius: ${themeCssVariables.border.radius.md};
|
||||
box-shadow:
|
||||
0px 2px 4px 0px ${themeCssVariables.background.transparent.light},
|
||||
0px 0px 4px 0px ${themeCssVariables.background.transparent.medium};
|
||||
display: inline-flex;
|
||||
`;
|
||||
|
||||
@@ -24,31 +26,33 @@ export const FloatingButtonGroup = ({
|
||||
children,
|
||||
size,
|
||||
className,
|
||||
}: FloatingButtonGroupProps) => (
|
||||
<StyledFloatingButtonGroupContainer className={className}>
|
||||
{React.Children.map(children, (child, index) => {
|
||||
let position: FloatingButtonPosition;
|
||||
}: FloatingButtonGroupProps) => {
|
||||
return (
|
||||
<StyledFloatingButtonGroupContainer className={className}>
|
||||
{React.Children.map(children, (child, index) => {
|
||||
let position: FloatingButtonPosition;
|
||||
|
||||
if (index === 0) {
|
||||
position = 'left';
|
||||
} else if (index === children.length - 1) {
|
||||
position = 'right';
|
||||
} else {
|
||||
position = 'middle';
|
||||
}
|
||||
if (index === 0) {
|
||||
position = 'left';
|
||||
} else if (index === children.length - 1) {
|
||||
position = 'right';
|
||||
} else {
|
||||
position = 'middle';
|
||||
}
|
||||
|
||||
const additionalProps: any = {
|
||||
position,
|
||||
size,
|
||||
applyShadow: false,
|
||||
applyBlur: false,
|
||||
};
|
||||
const additionalProps: any = {
|
||||
position,
|
||||
size,
|
||||
applyShadow: false,
|
||||
applyBlur: false,
|
||||
};
|
||||
|
||||
if (isDefined(size)) {
|
||||
additionalProps.size = size;
|
||||
}
|
||||
if (isDefined(size)) {
|
||||
additionalProps.size = size;
|
||||
}
|
||||
|
||||
return React.cloneElement(child, additionalProps);
|
||||
})}
|
||||
</StyledFloatingButtonGroupContainer>
|
||||
);
|
||||
return React.cloneElement(child, additionalProps);
|
||||
})}
|
||||
</StyledFloatingButtonGroupContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { css, useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type IconComponent } from '@ui/display';
|
||||
import React from 'react';
|
||||
import { ThemeContext, themeCssVariables } from '@ui/theme';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
export type FloatingIconButtonSize = 'small' | 'medium';
|
||||
export type FloatingIconButtonPosition =
|
||||
@@ -22,92 +22,81 @@ export type FloatingIconButtonProps = {
|
||||
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
isActive?: boolean;
|
||||
};
|
||||
const shouldForwardProp = (prop: string) =>
|
||||
![
|
||||
'applyBlur',
|
||||
'applyShadow',
|
||||
'isActive',
|
||||
'focus',
|
||||
'position',
|
||||
'size',
|
||||
].includes(prop);
|
||||
|
||||
const StyledButton = styled('button', { shouldForwardProp })<
|
||||
const StyledButton = styled.button<
|
||||
Pick<
|
||||
FloatingIconButtonProps,
|
||||
'size' | 'position' | 'applyShadow' | 'applyBlur' | 'focus' | 'isActive'
|
||||
>
|
||||
>`
|
||||
align-items: center;
|
||||
backdrop-filter: ${({ theme, applyBlur }) =>
|
||||
applyBlur ? theme.blur.medium : 'none'};
|
||||
background: ${({ theme, isActive }) =>
|
||||
isActive ? theme.background.transparent.medium : theme.background.primary};
|
||||
border: ${({ focus, theme }) =>
|
||||
backdrop-filter: ${({ applyBlur }) =>
|
||||
applyBlur ? themeCssVariables.blur.medium : 'none'};
|
||||
background: ${({ isActive }) =>
|
||||
isActive
|
||||
? themeCssVariables.background.transparent.medium
|
||||
: themeCssVariables.background.primary};
|
||||
border: ${({ focus }) =>
|
||||
focus
|
||||
? `1px solid ${theme.color.blue}`
|
||||
: `1px solid ${theme.border.color.strong}`};
|
||||
border-radius: ${({ position, theme }) => {
|
||||
? `1px solid ${themeCssVariables.color.blue}`
|
||||
: `1px solid ${themeCssVariables.border.color.strong}`};
|
||||
border-radius: ${({ position }) => {
|
||||
switch (position) {
|
||||
case 'left':
|
||||
return `${theme.border.radius.sm} 0px 0px ${theme.border.radius.sm}`;
|
||||
return `${themeCssVariables.border.radius.sm} 0px 0px ${themeCssVariables.border.radius.sm}`;
|
||||
case 'right':
|
||||
return `0px ${theme.border.radius.sm} ${theme.border.radius.sm} 0px`;
|
||||
return `0px ${themeCssVariables.border.radius.sm} ${themeCssVariables.border.radius.sm} 0px`;
|
||||
case 'middle':
|
||||
return '0px';
|
||||
case 'standalone':
|
||||
return theme.border.radius.sm;
|
||||
return themeCssVariables.border.radius.sm;
|
||||
}
|
||||
return '';
|
||||
}};
|
||||
box-shadow: ${({ theme, applyShadow, focus }) =>
|
||||
box-shadow: ${({ applyShadow, focus }) =>
|
||||
applyShadow
|
||||
? theme.boxShadow.light
|
||||
? themeCssVariables.boxShadow.light
|
||||
: focus
|
||||
? `0 0 0 3px ${theme.color.blue3}`
|
||||
? `0 0 0 3px ${themeCssVariables.color.blue3}`
|
||||
: 'none'};
|
||||
box-sizing: border-box;
|
||||
color: ${({ theme, disabled, focus }) => {
|
||||
color: ${({ disabled, focus }) => {
|
||||
return !disabled
|
||||
? focus
|
||||
? theme.color.blue
|
||||
: theme.font.color.tertiary
|
||||
: theme.font.color.extraLight;
|
||||
? themeCssVariables.color.blue
|
||||
: themeCssVariables.font.color.tertiary
|
||||
: themeCssVariables.font.color.extraLight;
|
||||
}};
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
font-family: ${themeCssVariables.font.family};
|
||||
font-weight: ${themeCssVariables.font.weight.regular};
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
transition: background ${({ theme }) => theme.animation.duration.instant}s
|
||||
ease;
|
||||
transition: background
|
||||
calc(${themeCssVariables.animation.duration.instant} * 1s) ease;
|
||||
white-space: nowrap;
|
||||
|
||||
${({ position, size }) => {
|
||||
const sizeInPx =
|
||||
(size === 'small' ? 24 : 32) - (position === 'standalone' ? 0 : 4);
|
||||
height: ${({ position, size }) =>
|
||||
(size === 'small' ? 24 : 32) - (position === 'standalone' ? 0 : 4)}px;
|
||||
width: ${({ position, size }) =>
|
||||
(size === 'small' ? 24 : 32) - (position === 'standalone' ? 0 : 4)}px;
|
||||
|
||||
return `
|
||||
height: ${sizeInPx}px;
|
||||
width: ${sizeInPx}px;
|
||||
`;
|
||||
}}
|
||||
|
||||
${({ theme, disabled }) =>
|
||||
!disabled &&
|
||||
css`
|
||||
&:hover {
|
||||
background: ${theme.background.transparent.lighter};
|
||||
}
|
||||
`}
|
||||
&:hover {
|
||||
background: ${({ disabled }) =>
|
||||
!disabled
|
||||
? themeCssVariables.background.transparent.lighter
|
||||
: 'transparent'};
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: ${({ theme, disabled }) =>
|
||||
!disabled ? theme.background.transparent.medium : 'transparent'};
|
||||
background: ${({ disabled }) =>
|
||||
!disabled
|
||||
? themeCssVariables.background.transparent.medium
|
||||
: 'transparent'};
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@@ -127,7 +116,7 @@ export const FloatingIconButton = ({
|
||||
onClick,
|
||||
isActive,
|
||||
}: FloatingIconButtonProps) => {
|
||||
const theme = useTheme();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
return (
|
||||
<StyledButton
|
||||
disabled={disabled}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type IconComponent } from '@ui/display';
|
||||
import { type MouseEvent } from 'react';
|
||||
|
||||
@@ -7,13 +7,15 @@ import {
|
||||
type FloatingIconButtonPosition,
|
||||
type FloatingIconButtonProps,
|
||||
} from './FloatingIconButton';
|
||||
import { themeCssVariables } from '@ui/theme';
|
||||
|
||||
const StyledFloatingIconButtonGroupContainer = styled.div`
|
||||
backdrop-filter: blur(20px);
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
box-shadow: ${({ theme }) =>
|
||||
`0px 2px 4px 0px ${theme.background.transparent.light}, 0px 0px 4px 0px ${theme.background.transparent.medium}`};
|
||||
background-color: ${themeCssVariables.background.primary};
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
box-shadow:
|
||||
0px 2px 4px 0px ${themeCssVariables.background.transparent.light},
|
||||
0px 0px 4px 0px ${themeCssVariables.background.transparent.medium};
|
||||
display: inline-flex;
|
||||
gap: 2px;
|
||||
padding: 2px;
|
||||
@@ -34,30 +36,32 @@ export const FloatingIconButtonGroup = ({
|
||||
iconButtons,
|
||||
size,
|
||||
className,
|
||||
}: FloatingIconButtonGroupProps) => (
|
||||
<StyledFloatingIconButtonGroupContainer className={className}>
|
||||
{iconButtons.map(({ Icon, onClick, isActive }, index) => {
|
||||
const position: FloatingIconButtonPosition =
|
||||
iconButtons.length === 1
|
||||
? 'standalone'
|
||||
: index === 0
|
||||
? 'left'
|
||||
: index === iconButtons.length - 1
|
||||
? 'right'
|
||||
: 'middle';
|
||||
}: FloatingIconButtonGroupProps) => {
|
||||
return (
|
||||
<StyledFloatingIconButtonGroupContainer className={className}>
|
||||
{iconButtons.map(({ Icon, onClick, isActive }, index) => {
|
||||
const position: FloatingIconButtonPosition =
|
||||
iconButtons.length === 1
|
||||
? 'standalone'
|
||||
: index === 0
|
||||
? 'left'
|
||||
: index === iconButtons.length - 1
|
||||
? 'right'
|
||||
: 'middle';
|
||||
|
||||
return (
|
||||
<FloatingIconButton
|
||||
key={`floating-icon-button-${index}`}
|
||||
applyBlur={false}
|
||||
applyShadow={false}
|
||||
Icon={Icon}
|
||||
onClick={onClick}
|
||||
position={position}
|
||||
size={size}
|
||||
isActive={isActive}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</StyledFloatingIconButtonGroupContainer>
|
||||
);
|
||||
return (
|
||||
<FloatingIconButton
|
||||
key={`floating-icon-button-${index}`}
|
||||
applyBlur={false}
|
||||
applyShadow={false}
|
||||
Icon={Icon}
|
||||
onClick={onClick}
|
||||
position={position}
|
||||
size={size}
|
||||
isActive={isActive}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</StyledFloatingIconButtonGroupContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { css, useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type IconComponent } from '@ui/display';
|
||||
import { GRAY_SCALE_LIGHT } from '@ui/theme';
|
||||
import React from 'react';
|
||||
import { GRAY_SCALE_LIGHT, ThemeContext, themeCssVariables } from '@ui/theme';
|
||||
import React, { useContext, useMemo } from 'react';
|
||||
|
||||
export type IconButtonSize = 'medium' | 'small';
|
||||
export type IconButtonPosition = 'standalone' | 'left' | 'middle' | 'right';
|
||||
@@ -24,6 +23,192 @@ export type IconButtonProps = {
|
||||
to?: string;
|
||||
};
|
||||
|
||||
type IconButtonDynamicStyles = {
|
||||
background: string;
|
||||
borderColor: string;
|
||||
borderWidthOverride?: string;
|
||||
boxShadow: string;
|
||||
color: string;
|
||||
opacity: number;
|
||||
hoverBackground: string;
|
||||
activeBackground: string;
|
||||
};
|
||||
|
||||
const computeIconButtonDynamicStyles = (
|
||||
variant: IconButtonVariant,
|
||||
accent: IconButtonAccent,
|
||||
disabled: boolean,
|
||||
focus: boolean,
|
||||
): IconButtonDynamicStyles => {
|
||||
const focusOverride = !disabled && focus;
|
||||
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
switch (accent) {
|
||||
case 'default':
|
||||
return {
|
||||
background: themeCssVariables.background.secondary,
|
||||
borderColor: focus
|
||||
? themeCssVariables.color.blue
|
||||
: themeCssVariables.background.transparent.light,
|
||||
borderWidthOverride: focusOverride ? '1px 1px' : undefined,
|
||||
boxShadow: focusOverride
|
||||
? `0 0 0 3px ${themeCssVariables.accent.tertiary}`
|
||||
: 'none',
|
||||
color: !disabled
|
||||
? themeCssVariables.font.color.secondary
|
||||
: themeCssVariables.font.color.extraLight,
|
||||
opacity: 1,
|
||||
hoverBackground: !disabled
|
||||
? themeCssVariables.background.tertiary
|
||||
: themeCssVariables.background.secondary,
|
||||
activeBackground: !disabled
|
||||
? themeCssVariables.background.quaternary
|
||||
: themeCssVariables.background.secondary,
|
||||
};
|
||||
case 'blue':
|
||||
return {
|
||||
background: themeCssVariables.color.blue,
|
||||
borderColor: !disabled
|
||||
? focus
|
||||
? themeCssVariables.color.blue
|
||||
: themeCssVariables.background.transparent.light
|
||||
: 'transparent',
|
||||
borderWidthOverride: focusOverride ? '1px 1px' : undefined,
|
||||
boxShadow: focusOverride
|
||||
? `0 0 0 3px ${themeCssVariables.accent.tertiary}`
|
||||
: 'none',
|
||||
color: GRAY_SCALE_LIGHT.gray1,
|
||||
opacity: disabled ? 0.24 : 1,
|
||||
hoverBackground: disabled
|
||||
? themeCssVariables.color.blue
|
||||
: themeCssVariables.color.blue10,
|
||||
activeBackground: disabled
|
||||
? themeCssVariables.color.blue
|
||||
: themeCssVariables.color.blue12,
|
||||
};
|
||||
case 'danger':
|
||||
return {
|
||||
background: themeCssVariables.color.red,
|
||||
borderColor: !disabled
|
||||
? focus
|
||||
? themeCssVariables.color.red
|
||||
: themeCssVariables.background.transparent.light
|
||||
: 'transparent',
|
||||
borderWidthOverride: focusOverride ? '1px 1px' : undefined,
|
||||
boxShadow: focusOverride
|
||||
? `0 0 0 3px ${themeCssVariables.color.red3}`
|
||||
: 'none',
|
||||
color: GRAY_SCALE_LIGHT.gray1,
|
||||
opacity: disabled ? 0.24 : 1,
|
||||
hoverBackground: disabled
|
||||
? themeCssVariables.color.red
|
||||
: themeCssVariables.color.red10,
|
||||
activeBackground: disabled
|
||||
? themeCssVariables.color.red
|
||||
: themeCssVariables.color.red10,
|
||||
};
|
||||
}
|
||||
break;
|
||||
case 'secondary':
|
||||
case 'tertiary':
|
||||
switch (accent) {
|
||||
case 'default':
|
||||
return {
|
||||
background: focus
|
||||
? themeCssVariables.background.transparent.primary
|
||||
: 'transparent',
|
||||
borderColor:
|
||||
variant === 'secondary'
|
||||
? focusOverride
|
||||
? themeCssVariables.color.blue
|
||||
: themeCssVariables.background.transparent.medium
|
||||
: focus
|
||||
? themeCssVariables.color.blue
|
||||
: 'transparent',
|
||||
borderWidthOverride: focusOverride ? '1px 1px' : undefined,
|
||||
boxShadow: focusOverride
|
||||
? `0 0 0 3px ${themeCssVariables.accent.tertiary}`
|
||||
: 'none',
|
||||
color: disabled
|
||||
? themeCssVariables.font.color.extraLight
|
||||
: variant === 'secondary'
|
||||
? themeCssVariables.font.color.secondary
|
||||
: themeCssVariables.font.color.tertiary,
|
||||
opacity: 1,
|
||||
hoverBackground: !disabled
|
||||
? themeCssVariables.background.transparent.light
|
||||
: 'transparent',
|
||||
activeBackground: !disabled
|
||||
? themeCssVariables.background.transparent.light
|
||||
: 'transparent',
|
||||
};
|
||||
case 'blue':
|
||||
return {
|
||||
background: focus
|
||||
? themeCssVariables.background.transparent.primary
|
||||
: 'transparent',
|
||||
borderColor:
|
||||
variant === 'secondary'
|
||||
? !disabled
|
||||
? themeCssVariables.color.blue
|
||||
: themeCssVariables.color.blue5
|
||||
: focus
|
||||
? themeCssVariables.color.blue
|
||||
: 'transparent',
|
||||
borderWidthOverride: focusOverride ? '1px 1px' : undefined,
|
||||
boxShadow: focusOverride
|
||||
? `0 0 0 3px ${themeCssVariables.accent.tertiary}`
|
||||
: 'none',
|
||||
color: !disabled
|
||||
? themeCssVariables.color.blue
|
||||
: themeCssVariables.accent.accent4060,
|
||||
opacity: 1,
|
||||
hoverBackground: !disabled
|
||||
? themeCssVariables.accent.tertiary
|
||||
: 'transparent',
|
||||
activeBackground: !disabled
|
||||
? themeCssVariables.accent.secondary
|
||||
: 'transparent',
|
||||
};
|
||||
case 'danger':
|
||||
return {
|
||||
background: 'transparent',
|
||||
borderColor:
|
||||
variant === 'secondary'
|
||||
? themeCssVariables.border.color.danger
|
||||
: focus
|
||||
? themeCssVariables.color.red
|
||||
: 'transparent',
|
||||
borderWidthOverride: focusOverride ? '1px 1px' : undefined,
|
||||
boxShadow: focusOverride
|
||||
? `0 0 0 3px ${themeCssVariables.color.red3}`
|
||||
: 'none',
|
||||
color: !disabled
|
||||
? themeCssVariables.font.color.danger
|
||||
: themeCssVariables.color.red5,
|
||||
opacity: 1,
|
||||
hoverBackground: !disabled
|
||||
? themeCssVariables.background.danger
|
||||
: 'transparent',
|
||||
activeBackground: !disabled
|
||||
? themeCssVariables.background.danger
|
||||
: 'transparent',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
background: 'transparent',
|
||||
borderColor: 'transparent',
|
||||
boxShadow: 'none',
|
||||
color: themeCssVariables.font.color.secondary,
|
||||
opacity: 1,
|
||||
hoverBackground: 'transparent',
|
||||
activeBackground: 'transparent',
|
||||
};
|
||||
};
|
||||
|
||||
const StyledButton = styled.button<
|
||||
Pick<
|
||||
IconButtonProps,
|
||||
@@ -31,206 +216,52 @@ const StyledButton = styled.button<
|
||||
>
|
||||
>`
|
||||
align-items: center;
|
||||
${({ theme, variant, accent, disabled, focus }) => {
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
switch (accent) {
|
||||
case 'default':
|
||||
return css`
|
||||
background: ${theme.background.secondary};
|
||||
border-color: ${focus
|
||||
? theme.color.blue
|
||||
: theme.background.transparent.light};
|
||||
border-width: ${!disabled && focus ? '1px 1px !important' : 0};
|
||||
box-shadow: ${!disabled && focus
|
||||
? `0 0 0 3px ${theme.accent.tertiary}`
|
||||
: 'none'};
|
||||
color: ${!disabled
|
||||
? theme.font.color.secondary
|
||||
: theme.font.color.extraLight};
|
||||
&:hover {
|
||||
background: ${!disabled
|
||||
? theme.background.tertiary
|
||||
: theme.background.secondary};
|
||||
}
|
||||
&:active {
|
||||
background: ${!disabled
|
||||
? theme.background.quaternary
|
||||
: theme.background.secondary};
|
||||
}
|
||||
`;
|
||||
case 'blue':
|
||||
return css`
|
||||
background: ${theme.color.blue};
|
||||
border-color: ${!disabled
|
||||
? focus
|
||||
? theme.color.blue
|
||||
: theme.background.transparent.light
|
||||
: 'transparent'};
|
||||
border-width: ${!disabled && focus ? '1px 1px !important' : 0};
|
||||
box-shadow: ${!disabled && focus
|
||||
? `0 0 0 3px ${theme.accent.tertiary}`
|
||||
: 'none'};
|
||||
color: ${GRAY_SCALE_LIGHT.gray1};
|
||||
opacity: ${disabled ? 0.24 : 1};
|
||||
background: var(--ibtn-bg);
|
||||
border-color: var(--ibtn-border-color);
|
||||
box-shadow: var(--ibtn-box-shadow);
|
||||
color: var(--ibtn-color);
|
||||
opacity: var(--ibtn-opacity, 1);
|
||||
&:hover {
|
||||
background: var(--ibtn-hover-bg);
|
||||
}
|
||||
&:active {
|
||||
background: var(--ibtn-active-bg);
|
||||
}
|
||||
|
||||
${disabled
|
||||
? ''
|
||||
: css`
|
||||
&:hover {
|
||||
background: ${theme.color.blue10};
|
||||
}
|
||||
&:active {
|
||||
background: ${theme.color.blue12};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
case 'danger':
|
||||
return css`
|
||||
background: ${theme.color.red};
|
||||
border-color: ${!disabled
|
||||
? focus
|
||||
? theme.color.red
|
||||
: theme.background.transparent.light
|
||||
: 'transparent'};
|
||||
border-width: ${!disabled && focus ? '1px 1px !important' : 0};
|
||||
box-shadow: ${!disabled && focus
|
||||
? `0 0 0 3px ${theme.color.red3}`
|
||||
: 'none'};
|
||||
color: ${GRAY_SCALE_LIGHT.gray1};
|
||||
opacity: ${disabled ? 0.24 : 1};
|
||||
|
||||
${disabled
|
||||
? ''
|
||||
: css`
|
||||
&:hover,
|
||||
&:active {
|
||||
background: ${theme.color.red10};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
}
|
||||
break;
|
||||
case 'secondary':
|
||||
case 'tertiary':
|
||||
switch (accent) {
|
||||
case 'default':
|
||||
return css`
|
||||
background: ${focus
|
||||
? theme.background.transparent.primary
|
||||
: 'transparent'};
|
||||
border-color: ${variant === 'secondary'
|
||||
? !disabled && focus
|
||||
? theme.color.blue
|
||||
: theme.background.transparent.medium
|
||||
: focus
|
||||
? theme.color.blue
|
||||
: 'transparent'};
|
||||
border-width: ${!disabled && focus ? '1px 1px !important' : 0};
|
||||
box-shadow: ${!disabled && focus
|
||||
? `0 0 0 3px ${theme.accent.tertiary}`
|
||||
: 'none'};
|
||||
color: ${disabled
|
||||
? theme.font.color.extraLight
|
||||
: variant === 'secondary'
|
||||
? theme.font.color.secondary
|
||||
: theme.font.color.tertiary};
|
||||
&:hover {
|
||||
background: ${!disabled
|
||||
? theme.background.transparent.light
|
||||
: 'transparent'};
|
||||
}
|
||||
&:active {
|
||||
background: ${!disabled
|
||||
? theme.background.transparent.light
|
||||
: 'transparent'};
|
||||
}
|
||||
`;
|
||||
case 'blue':
|
||||
return css`
|
||||
background: ${focus
|
||||
? theme.background.transparent.primary
|
||||
: 'transparent'};
|
||||
border-color: ${variant === 'secondary'
|
||||
? !disabled
|
||||
? theme.color.blue
|
||||
: theme.color.blue5
|
||||
: focus
|
||||
? theme.color.blue
|
||||
: 'transparent'};
|
||||
border-width: ${!disabled && focus ? '1px 1px !important' : 0};
|
||||
box-shadow: ${!disabled && focus
|
||||
? `0 0 0 3px ${theme.accent.tertiary}`
|
||||
: 'none'};
|
||||
color: ${!disabled ? theme.color.blue : theme.accent.accent4060};
|
||||
&:hover {
|
||||
background: ${!disabled
|
||||
? theme.accent.tertiary
|
||||
: 'transparent'};
|
||||
}
|
||||
&:active {
|
||||
background: ${!disabled
|
||||
? theme.accent.secondary
|
||||
: 'transparent'};
|
||||
}
|
||||
`;
|
||||
case 'danger':
|
||||
return css`
|
||||
background: transparent;
|
||||
border-color: ${variant === 'secondary'
|
||||
? theme.border.color.danger
|
||||
: focus
|
||||
? theme.color.red
|
||||
: 'transparent'};
|
||||
border-width: ${!disabled && focus ? '1px 1px !important' : 0};
|
||||
box-shadow: ${!disabled && focus
|
||||
? `0 0 0 3px ${theme.color.red3}`
|
||||
: 'none'};
|
||||
color: ${!disabled ? theme.font.color.danger : theme.color.red5};
|
||||
&:hover {
|
||||
background: ${!disabled
|
||||
? theme.background.danger
|
||||
: 'transparent'};
|
||||
}
|
||||
&:active {
|
||||
background: ${!disabled
|
||||
? theme.background.danger
|
||||
: 'transparent'};
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
border-radius: ${({ position, theme }) => {
|
||||
border-radius: ${({ position }) => {
|
||||
switch (position) {
|
||||
case 'left':
|
||||
return `${theme.border.radius.sm} 0px 0px ${theme.border.radius.sm}`;
|
||||
return `${themeCssVariables.border.radius.sm} 0px 0px ${themeCssVariables.border.radius.sm}`;
|
||||
case 'right':
|
||||
return `0px ${theme.border.radius.sm} ${theme.border.radius.sm} 0px`;
|
||||
return `0px ${themeCssVariables.border.radius.sm} ${themeCssVariables.border.radius.sm} 0px`;
|
||||
case 'middle':
|
||||
return '0px';
|
||||
case 'standalone':
|
||||
return theme.border.radius.sm;
|
||||
return themeCssVariables.border.radius.sm;
|
||||
}
|
||||
return '';
|
||||
}};
|
||||
border-style: solid;
|
||||
border-width: ${({ variant, position }) => {
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
case 'secondary':
|
||||
return position === 'middle' ? '1px 0px' : '1px';
|
||||
case 'tertiary':
|
||||
return '0';
|
||||
}
|
||||
}};
|
||||
border-width: var(
|
||||
--ibtn-border-width,
|
||||
${({ variant, position }) => {
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
case 'secondary':
|
||||
return position === 'middle' ? '1px 0px' : '1px';
|
||||
case 'tertiary':
|
||||
return '0';
|
||||
}
|
||||
return '';
|
||||
}}
|
||||
);
|
||||
box-sizing: border-box;
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
font-family: ${themeCssVariables.font.family};
|
||||
font-weight: 500;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
height: ${({ size }) => (size === 'small' ? '24px' : '32px')};
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
@@ -259,7 +290,26 @@ export const IconButton = ({
|
||||
onClick,
|
||||
to,
|
||||
}: IconButtonProps) => {
|
||||
const theme = useTheme();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const dynamicStyles = useMemo(() => {
|
||||
const styles = computeIconButtonDynamicStyles(
|
||||
variant,
|
||||
accent,
|
||||
disabled,
|
||||
focus,
|
||||
);
|
||||
return {
|
||||
'--ibtn-bg': styles.background,
|
||||
'--ibtn-border-color': styles.borderColor,
|
||||
'--ibtn-border-width': styles.borderWidthOverride || undefined,
|
||||
'--ibtn-box-shadow': styles.boxShadow,
|
||||
'--ibtn-color': styles.color,
|
||||
'--ibtn-opacity': styles.opacity,
|
||||
'--ibtn-hover-bg': styles.hoverBackground,
|
||||
'--ibtn-active-bg': styles.activeBackground,
|
||||
} as React.CSSProperties;
|
||||
}, [variant, accent, disabled, focus]);
|
||||
|
||||
return (
|
||||
<StyledButton
|
||||
data-testid={dataTestId}
|
||||
@@ -273,6 +323,7 @@ export const IconButton = ({
|
||||
onClick={onClick}
|
||||
aria-label={ariaLabel}
|
||||
to={to}
|
||||
style={dynamicStyles}
|
||||
>
|
||||
{Icon && <Icon size={theme.icon.size.md} />}
|
||||
</StyledButton>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type IconComponent } from '@ui/display';
|
||||
import { type MouseEvent } from 'react';
|
||||
|
||||
import { InsideButton } from '@ui/input/button/components/InsideButton';
|
||||
import { themeCssVariables } from '@ui/theme';
|
||||
|
||||
export type IconButtonGroupProps = {
|
||||
disabled?: boolean;
|
||||
@@ -18,16 +19,16 @@ const StyledIconButtonGroupContainer = styled.div<
|
||||
>`
|
||||
display: inline-flex;
|
||||
align-items: flex-start;
|
||||
background-color: ${({ disabled, theme }) =>
|
||||
disabled ? 'inherit' : theme.background.transparent.lighter};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.strong};
|
||||
background-color: ${({ disabled }) =>
|
||||
disabled ? 'inherit' : themeCssVariables.background.transparent.lighter};
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
border: 1px solid ${themeCssVariables.border.color.strong};
|
||||
gap: 2px;
|
||||
padding: 2px;
|
||||
backdrop-filter: blur(20px);
|
||||
|
||||
&:hover {
|
||||
box-shadow: ${({ theme }) => theme.boxShadow.light};
|
||||
box-shadow: ${themeCssVariables.boxShadow.light};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -35,17 +36,19 @@ export const IconButtonGroup = ({
|
||||
iconButtons,
|
||||
disabled,
|
||||
className,
|
||||
}: IconButtonGroupProps) => (
|
||||
<StyledIconButtonGroupContainer className={className} disabled={disabled}>
|
||||
{iconButtons.map(({ Icon, onClick }, index) => {
|
||||
return (
|
||||
<InsideButton
|
||||
key={index}
|
||||
Icon={Icon}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</StyledIconButtonGroupContainer>
|
||||
);
|
||||
}: IconButtonGroupProps) => {
|
||||
return (
|
||||
<StyledIconButtonGroupContainer className={className} disabled={disabled}>
|
||||
{iconButtons.map(({ Icon, onClick }, index) => {
|
||||
return (
|
||||
<InsideButton
|
||||
key={index}
|
||||
Icon={Icon}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</StyledIconButtonGroupContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { type IconComponent } from '@ui/display';
|
||||
import styled from '@emotion/styled';
|
||||
import React from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { ThemeContext, themeCssVariables } from '@ui/theme';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
export type InsideButtonProps = {
|
||||
className?: string;
|
||||
@@ -14,8 +14,8 @@ const StyledButton = styled.button`
|
||||
align-items: center;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
border-radius: ${({ theme }) => theme.border.radius.xs};
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
border-radius: ${themeCssVariables.border.radius.xs};
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -27,7 +27,7 @@ const StyledButton = styled.button`
|
||||
transition: background-color 0.1s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.background.transparent.light};
|
||||
background-color: ${themeCssVariables.background.transparent.light};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -37,7 +37,7 @@ export const InsideButton = ({
|
||||
onClick,
|
||||
disabled = false,
|
||||
}: InsideButtonProps) => {
|
||||
const theme = useTheme();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<StyledButton className={className} onClick={onClick} disabled={disabled}>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type IconComponent } from '@ui/display';
|
||||
import { type MouseEvent } from 'react';
|
||||
import { ThemeContext, themeCssVariables } from '@ui/theme';
|
||||
import { type MouseEvent, useContext } from 'react';
|
||||
|
||||
export type LightButtonAccent = 'secondary' | 'tertiary';
|
||||
|
||||
@@ -22,47 +22,48 @@ const StyledButton = styled.button<
|
||||
>`
|
||||
align-items: center;
|
||||
background: transparent;
|
||||
border: ${({ theme, focus }) =>
|
||||
focus ? `1px solid ${theme.color.blue}` : 'none'};
|
||||
border: ${({ focus }) =>
|
||||
focus ? `1px solid ${themeCssVariables.color.blue}` : 'none'};
|
||||
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
box-shadow: ${({ theme, focus }) =>
|
||||
focus ? `0 0 0 3px ${theme.color.blue3}` : 'none'};
|
||||
color: ${({ theme, accent, active, disabled, focus }) => {
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
box-shadow: ${({ focus }) =>
|
||||
focus ? `0 0 0 3px ${themeCssVariables.color.blue3}` : 'none'};
|
||||
color: ${({ accent, active, disabled, focus }) => {
|
||||
switch (accent) {
|
||||
case 'secondary':
|
||||
return active || focus
|
||||
? theme.color.blue
|
||||
? themeCssVariables.color.blue
|
||||
: !disabled
|
||||
? theme.font.color.secondary
|
||||
: theme.font.color.extraLight;
|
||||
? themeCssVariables.font.color.secondary
|
||||
: themeCssVariables.font.color.extraLight;
|
||||
case 'tertiary':
|
||||
return active || focus
|
||||
? theme.color.blue
|
||||
? themeCssVariables.color.blue
|
||||
: !disabled
|
||||
? theme.font.color.tertiary
|
||||
: theme.font.color.extraLight;
|
||||
? themeCssVariables.font.color.tertiary
|
||||
: themeCssVariables.font.color.extraLight;
|
||||
}
|
||||
return '';
|
||||
}};
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
font-family: ${themeCssVariables.font.family};
|
||||
font-weight: ${themeCssVariables.font.weight.regular};
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
height: 24px;
|
||||
padding: ${({ theme }) => {
|
||||
return `0 ${theme.spacing(2)}`;
|
||||
}};
|
||||
padding: 0 ${themeCssVariables.spacing[2]};
|
||||
|
||||
transition: background 0.1s ease;
|
||||
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
background: ${({ theme, disabled }) =>
|
||||
!disabled ? theme.background.transparent.light : 'transparent'};
|
||||
background: ${({ disabled }) =>
|
||||
!disabled
|
||||
? themeCssVariables.background.transparent.light
|
||||
: 'transparent'};
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@@ -70,8 +71,10 @@ const StyledButton = styled.button<
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: ${({ theme, disabled }) =>
|
||||
!disabled ? theme.background.transparent.medium : 'transparent'};
|
||||
background: ${({ disabled }) =>
|
||||
!disabled
|
||||
? themeCssVariables.background.transparent.medium
|
||||
: 'transparent'};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -86,7 +89,7 @@ export const LightButton = ({
|
||||
type = 'button',
|
||||
onClick,
|
||||
}: LightButtonProps) => {
|
||||
const theme = useTheme();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<StyledButton
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type IconComponent } from '@ui/display';
|
||||
import { type ComponentProps, type MouseEvent } from 'react';
|
||||
import { ThemeContext, themeCssVariables } from '@ui/theme';
|
||||
import { type ComponentProps, type MouseEvent, useContext } from 'react';
|
||||
|
||||
export type LightIconButtonAccent = 'secondary' | 'tertiary';
|
||||
export type LightIconButtonSize = 'small' | 'medium';
|
||||
@@ -26,49 +26,58 @@ const StyledButton = styled.button<
|
||||
background: transparent;
|
||||
border: none;
|
||||
|
||||
border: ${({ disabled, theme, focus }) =>
|
||||
!disabled && focus ? `1px solid ${theme.color.blue}` : 'none'};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
box-shadow: ${({ disabled, theme, focus }) =>
|
||||
!disabled && focus ? `0 0 0 3px ${theme.color.blue3}` : 'none'};
|
||||
color: ${({ theme, accent, active, disabled, focus }) => {
|
||||
border: ${({ disabled, focus }) =>
|
||||
!disabled && focus ? `1px solid ${themeCssVariables.color.blue}` : 'none'};
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
box-shadow: ${({ disabled, focus }) =>
|
||||
!disabled && focus ? `0 0 0 3px ${themeCssVariables.color.blue3}` : 'none'};
|
||||
color: ${({ accent, active, disabled, focus }) => {
|
||||
switch (accent) {
|
||||
case 'secondary':
|
||||
return active || focus
|
||||
? theme.color.blue
|
||||
? themeCssVariables.color.blue
|
||||
: !disabled
|
||||
? theme.font.color.secondary
|
||||
: theme.font.color.extraLight;
|
||||
? themeCssVariables.font.color.secondary
|
||||
: themeCssVariables.font.color.extraLight;
|
||||
case 'tertiary':
|
||||
return active || focus
|
||||
? theme.color.blue
|
||||
? themeCssVariables.color.blue
|
||||
: !disabled
|
||||
? theme.font.color.tertiary
|
||||
: theme.font.color.extraLight;
|
||||
? themeCssVariables.font.color.tertiary
|
||||
: themeCssVariables.font.color.extraLight;
|
||||
}
|
||||
return '';
|
||||
}};
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
height: ${({ size, theme }) =>
|
||||
size === 'small' ? theme.spacing(6) : theme.spacing(8)};
|
||||
font-family: ${themeCssVariables.font.family};
|
||||
font-weight: ${themeCssVariables.font.weight.regular};
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
height: ${({ size }) =>
|
||||
size === 'small'
|
||||
? themeCssVariables.spacing[6]
|
||||
: themeCssVariables.spacing[8]};
|
||||
justify-content: center;
|
||||
padding: ${({ theme }) => theme.spacing(1)};
|
||||
padding: ${themeCssVariables.spacing[1]};
|
||||
transition: background 0.1s ease;
|
||||
|
||||
white-space: nowrap;
|
||||
width: ${({ size, theme }) =>
|
||||
size === 'small' ? theme.spacing(6) : theme.spacing(8)};
|
||||
min-width: ${({ size, theme }) =>
|
||||
size === 'small' ? theme.spacing(6) : theme.spacing(8)};
|
||||
width: ${({ size }) =>
|
||||
size === 'small'
|
||||
? themeCssVariables.spacing[6]
|
||||
: themeCssVariables.spacing[8]};
|
||||
min-width: ${({ size }) =>
|
||||
size === 'small'
|
||||
? themeCssVariables.spacing[6]
|
||||
: themeCssVariables.spacing[8]};
|
||||
|
||||
&:hover {
|
||||
background: ${({ theme, disabled }) =>
|
||||
!disabled ? theme.background.transparent.light : 'transparent'};
|
||||
background: ${({ disabled }) =>
|
||||
!disabled
|
||||
? themeCssVariables.background.transparent.light
|
||||
: 'transparent'};
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@@ -76,8 +85,10 @@ const StyledButton = styled.button<
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: ${({ theme, disabled }) =>
|
||||
!disabled ? theme.background.transparent.medium : 'transparent'};
|
||||
background: ${({ disabled }) =>
|
||||
!disabled
|
||||
? themeCssVariables.background.transparent.medium
|
||||
: 'transparent'};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -94,7 +105,7 @@ export const LightIconButton = ({
|
||||
onClick,
|
||||
title,
|
||||
}: LightIconButtonProps) => {
|
||||
const theme = useTheme();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<StyledButton
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type IconComponent } from '@ui/display';
|
||||
import {
|
||||
type FunctionComponent,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type IconComponent } from '@ui/display';
|
||||
import React, { type FunctionComponent } from 'react';
|
||||
import { ThemeContext, themeCssVariables } from '@ui/theme';
|
||||
import React, { type FunctionComponent, useContext } from 'react';
|
||||
|
||||
export type MainButtonVariant = 'primary' | 'secondary';
|
||||
|
||||
@@ -17,89 +17,76 @@ const StyledButton = styled.button<
|
||||
Pick<Props, 'fullWidth' | 'width' | 'variant'>
|
||||
>`
|
||||
align-items: center;
|
||||
background: ${({ theme, variant, disabled }) => {
|
||||
background: ${({ variant, disabled }) => {
|
||||
if (disabled === true) {
|
||||
return theme.background.secondary;
|
||||
return themeCssVariables.background.secondary;
|
||||
}
|
||||
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
return theme.background.primaryInverted;
|
||||
return themeCssVariables.background.primaryInverted;
|
||||
case 'secondary':
|
||||
return theme.background.primary;
|
||||
return themeCssVariables.background.primary;
|
||||
default:
|
||||
return theme.background.primary;
|
||||
return themeCssVariables.background.primary;
|
||||
}
|
||||
}};
|
||||
border: 1px solid;
|
||||
border-color: ${({ theme, disabled, variant }) => {
|
||||
border-color: ${({ disabled, variant }) => {
|
||||
if (disabled === true) {
|
||||
return theme.background.transparent.lighter;
|
||||
return themeCssVariables.background.transparent.lighter;
|
||||
}
|
||||
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
return theme.background.transparent.strong;
|
||||
return themeCssVariables.background.transparent.strong;
|
||||
case 'secondary':
|
||||
return theme.border.color.medium;
|
||||
return themeCssVariables.border.color.medium;
|
||||
default:
|
||||
return theme.background.primary;
|
||||
return themeCssVariables.background.primary;
|
||||
}
|
||||
}};
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
${({ theme, disabled }) => {
|
||||
border-radius: ${themeCssVariables.border.radius.md};
|
||||
box-shadow: ${({ disabled }) =>
|
||||
disabled ? 'none' : themeCssVariables.boxShadow.light};
|
||||
color: ${({ variant, disabled }) => {
|
||||
if (disabled === true) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return `box-shadow: ${theme.boxShadow.light};`;
|
||||
}}
|
||||
color: ${({ theme, variant, disabled }) => {
|
||||
if (disabled === true) {
|
||||
return theme.font.color.light;
|
||||
return themeCssVariables.font.color.light;
|
||||
}
|
||||
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
return theme.font.color.inverted;
|
||||
return themeCssVariables.font.color.inverted;
|
||||
case 'secondary':
|
||||
return theme.font.color.primary;
|
||||
return themeCssVariables.font.color.primary;
|
||||
default:
|
||||
return theme.font.color.primary;
|
||||
return themeCssVariables.font.color.primary;
|
||||
}
|
||||
}};
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
font-family: ${themeCssVariables.font.family};
|
||||
font-weight: ${themeCssVariables.font.weight.semiBold};
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
justify-content: center;
|
||||
outline: none;
|
||||
padding: ${({ theme }) => theme.spacing(2)} ${({ theme }) => theme.spacing(3)};
|
||||
max-height: ${({ theme }) => theme.spacing(8)};
|
||||
padding: ${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[3]};
|
||||
max-height: ${themeCssVariables.spacing[8]};
|
||||
width: ${({ fullWidth, width }) =>
|
||||
fullWidth ? '100%' : width ? `${width}px` : 'auto'};
|
||||
${({ theme, variant, disabled }) => {
|
||||
switch (variant) {
|
||||
case 'secondary':
|
||||
return `
|
||||
&:hover {
|
||||
background: ${theme.background.tertiary};
|
||||
}
|
||||
`;
|
||||
default:
|
||||
return `
|
||||
&:hover {
|
||||
background: ${
|
||||
!disabled
|
||||
? theme.background.primaryInvertedHover
|
||||
: theme.background.secondary
|
||||
};};
|
||||
}
|
||||
`;
|
||||
}
|
||||
}};
|
||||
&:hover {
|
||||
background: ${({ variant, disabled }) => {
|
||||
switch (variant) {
|
||||
case 'secondary':
|
||||
return themeCssVariables.background.tertiary;
|
||||
default:
|
||||
return !disabled
|
||||
? themeCssVariables.background.primaryInvertedHover
|
||||
: themeCssVariables.background.secondary;
|
||||
}
|
||||
}};
|
||||
}
|
||||
`;
|
||||
|
||||
type MainButtonProps = Props & {
|
||||
@@ -117,7 +104,7 @@ export const MainButton = ({
|
||||
disabled,
|
||||
className,
|
||||
}: MainButtonProps) => {
|
||||
const theme = useTheme();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
return (
|
||||
<StyledButton
|
||||
className={className}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type IconComponent } from '@ui/display';
|
||||
import { ThemeContext, themeCssVariables } from '@ui/theme';
|
||||
import { useContext } from 'react';
|
||||
|
||||
export type RoundedIconButtonSize = 'small' | 'medium';
|
||||
|
||||
const StyledIconButton = styled.button<{ size: RoundedIconButtonSize }>`
|
||||
const StyledIconButton = styled.button<{
|
||||
size: RoundedIconButtonSize;
|
||||
}>`
|
||||
align-items: center;
|
||||
background: ${({ theme }) => theme.color.blue};
|
||||
background: ${themeCssVariables.color.blue};
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
color: ${({ theme }) => theme.font.color.inverted};
|
||||
color: ${themeCssVariables.font.color.inverted};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: ${({ size }) => (size === 'small' ? '20px' : '24px')};
|
||||
@@ -21,8 +24,8 @@ const StyledIconButton = styled.button<{ size: RoundedIconButtonSize }>`
|
||||
background 0.1s ease-in-out;
|
||||
|
||||
&:disabled {
|
||||
background: ${({ theme }) => theme.background.quaternary};
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
background: ${themeCssVariables.background.quaternary};
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
cursor: default;
|
||||
}
|
||||
width: ${({ size }) => (size === 'small' ? '20px' : '24px')};
|
||||
@@ -40,7 +43,7 @@ export const RoundedIconButton = ({
|
||||
className,
|
||||
size = 'small',
|
||||
}: RoundedIconButtonProps) => {
|
||||
const theme = useTheme();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<StyledIconButton
|
||||
|
||||
+25
-27
@@ -1,27 +1,25 @@
|
||||
import isPropValid from '@emotion/is-prop-valid';
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
import { themeCssVariables } from '@ui/theme';
|
||||
|
||||
export const StyledTabButton = styled('button', {
|
||||
shouldForwardProp: (prop) => isPropValid(prop) && prop !== 'active',
|
||||
})<{
|
||||
export const StyledTabButton = styled.button<{
|
||||
active?: boolean;
|
||||
disabled?: boolean;
|
||||
to?: string;
|
||||
}>`
|
||||
all: unset;
|
||||
align-items: center;
|
||||
color: ${({ theme, active, disabled }) =>
|
||||
color: ${({ active, disabled }) =>
|
||||
active
|
||||
? theme.font.color.primary
|
||||
? themeCssVariables.font.color.primary
|
||||
: disabled
|
||||
? theme.font.color.light
|
||||
: theme.font.color.secondary};
|
||||
? themeCssVariables.font.color.light
|
||||
: themeCssVariables.font.color.secondary};
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
font-family: inherit;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
justify-content: center;
|
||||
pointer-events: ${({ disabled }) => (disabled ? 'none' : '')};
|
||||
text-decoration: none;
|
||||
@@ -33,8 +31,8 @@ export const StyledTabButton = styled('button', {
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background-color: ${({ theme, active }) =>
|
||||
active ? theme.border.color.inverted : 'transparent'};
|
||||
background-color: ${({ active }) =>
|
||||
active ? themeCssVariables.border.color.inverted : 'transparent'};
|
||||
z-index: 1;
|
||||
}
|
||||
`;
|
||||
@@ -44,16 +42,16 @@ export const StyledTabContainer = styled.div<{
|
||||
disabled?: boolean;
|
||||
}>`
|
||||
align-items: center;
|
||||
color: ${({ theme, active, disabled }) =>
|
||||
color: ${({ active, disabled }) =>
|
||||
active
|
||||
? theme.font.color.primary
|
||||
? themeCssVariables.font.color.primary
|
||||
: disabled
|
||||
? theme.font.color.light
|
||||
: theme.font.color.secondary};
|
||||
? themeCssVariables.font.color.light
|
||||
: themeCssVariables.font.color.secondary};
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
@@ -65,8 +63,8 @@ export const StyledTabContainer = styled.div<{
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background-color: ${({ theme, active }) =>
|
||||
active ? theme.border.color.inverted : 'transparent'};
|
||||
background-color: ${({ active }) =>
|
||||
active ? themeCssVariables.border.color.inverted : 'transparent'};
|
||||
z-index: 1;
|
||||
}
|
||||
`;
|
||||
@@ -75,19 +73,19 @@ export const StyledTabHover = styled.span<{
|
||||
contentSize?: 'sm' | 'md';
|
||||
}>`
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
padding: ${({ theme, contentSize }) =>
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
padding: ${({ contentSize }) =>
|
||||
contentSize === 'sm'
|
||||
? `${theme.spacing(1)} ${theme.spacing(2)}`
|
||||
: `${theme.spacing(2)} ${theme.spacing(2)}`};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
? `${themeCssVariables.spacing[1]} ${themeCssVariables.spacing[2]}`
|
||||
: `${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[2]}`};
|
||||
font-weight: ${themeCssVariables.font.weight.medium};
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
&:hover {
|
||||
background: ${({ theme }) => theme.background.tertiary};
|
||||
background: ${themeCssVariables.background.tertiary};
|
||||
}
|
||||
&:active {
|
||||
background: ${({ theme }) => theme.background.quaternary};
|
||||
background: ${themeCssVariables.background.quaternary};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import styled from '@emotion/styled';
|
||||
/* eslint-disable react/jsx-props-no-spreading */
|
||||
import { styled } from '@linaria/react';
|
||||
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
||||
import {
|
||||
IconCheckbox,
|
||||
@@ -15,11 +16,12 @@ import {
|
||||
ComponentWithRouterDecorator,
|
||||
JotaiRootDecorator,
|
||||
} from '@ui/testing';
|
||||
import { type ReactNode } from 'react';
|
||||
import { themeCssVariables } from '@ui/theme';
|
||||
|
||||
// Mimic the TabList container styling for proper positioning
|
||||
const StyledTabContainer = styled.div`
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
height: 40px;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
@@ -32,10 +34,14 @@ const StyledTabContainer = styled.div`
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background-color: ${({ theme }) => theme.border.color.light};
|
||||
background-color: ${themeCssVariables.border.color.light};
|
||||
}
|
||||
`;
|
||||
|
||||
const TabContainer = ({ children }: { children?: ReactNode }) => {
|
||||
return <StyledTabContainer>{children}</StyledTabContainer>;
|
||||
};
|
||||
|
||||
const meta: Meta<typeof TabButton> = {
|
||||
title: 'UI/Input/Button/TabButton',
|
||||
component: TabButton,
|
||||
@@ -67,22 +73,9 @@ export const Default: Story = {
|
||||
LeftIcon: IconSettings,
|
||||
},
|
||||
render: (args) => (
|
||||
<StyledTabContainer>
|
||||
<TabButton
|
||||
id={args.id}
|
||||
title={args.title}
|
||||
LeftIcon={args.LeftIcon}
|
||||
RightIcon={args.RightIcon}
|
||||
active={args.active}
|
||||
disabled={args.disabled}
|
||||
pill={args.pill}
|
||||
to={args.to}
|
||||
logo={args.logo}
|
||||
onClick={args.onClick}
|
||||
className={args.className}
|
||||
contentSize={args.contentSize}
|
||||
/>
|
||||
</StyledTabContainer>
|
||||
<TabContainer>
|
||||
<TabButton {...args} />
|
||||
</TabContainer>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -93,22 +86,9 @@ export const Active: Story = {
|
||||
active: true,
|
||||
},
|
||||
render: (args) => (
|
||||
<StyledTabContainer>
|
||||
<TabButton
|
||||
id={args.id}
|
||||
title={args.title}
|
||||
LeftIcon={args.LeftIcon}
|
||||
RightIcon={args.RightIcon}
|
||||
active={args.active}
|
||||
disabled={args.disabled}
|
||||
pill={args.pill}
|
||||
to={args.to}
|
||||
logo={args.logo}
|
||||
onClick={args.onClick}
|
||||
className={args.className}
|
||||
contentSize={args.contentSize}
|
||||
/>
|
||||
</StyledTabContainer>
|
||||
<TabContainer>
|
||||
<TabButton {...args} />
|
||||
</TabContainer>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -119,22 +99,9 @@ export const Disabled: Story = {
|
||||
disabled: true,
|
||||
},
|
||||
render: (args) => (
|
||||
<StyledTabContainer>
|
||||
<TabButton
|
||||
id={args.id}
|
||||
title={args.title}
|
||||
LeftIcon={args.LeftIcon}
|
||||
RightIcon={args.RightIcon}
|
||||
active={args.active}
|
||||
disabled={args.disabled}
|
||||
pill={args.pill}
|
||||
to={args.to}
|
||||
logo={args.logo}
|
||||
onClick={args.onClick}
|
||||
className={args.className}
|
||||
contentSize={args.contentSize}
|
||||
/>
|
||||
</StyledTabContainer>
|
||||
<TabContainer>
|
||||
<TabButton {...args} />
|
||||
</TabContainer>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -144,22 +111,9 @@ export const WithLogo: Story = {
|
||||
logo: 'https://picsum.photos/192/192',
|
||||
},
|
||||
render: (args) => (
|
||||
<StyledTabContainer>
|
||||
<TabButton
|
||||
id={args.id}
|
||||
title={args.title}
|
||||
LeftIcon={args.LeftIcon}
|
||||
RightIcon={args.RightIcon}
|
||||
active={args.active}
|
||||
disabled={args.disabled}
|
||||
pill={args.pill}
|
||||
to={args.to}
|
||||
logo={args.logo}
|
||||
onClick={args.onClick}
|
||||
className={args.className}
|
||||
contentSize={args.contentSize}
|
||||
/>
|
||||
</StyledTabContainer>
|
||||
<TabContainer>
|
||||
<TabButton {...args} />
|
||||
</TabContainer>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -170,22 +124,9 @@ export const WithStringPill: Story = {
|
||||
pill: '12',
|
||||
},
|
||||
render: (args) => (
|
||||
<StyledTabContainer>
|
||||
<TabButton
|
||||
id={args.id}
|
||||
title={args.title}
|
||||
LeftIcon={args.LeftIcon}
|
||||
RightIcon={args.RightIcon}
|
||||
active={args.active}
|
||||
disabled={args.disabled}
|
||||
pill={args.pill}
|
||||
to={args.to}
|
||||
logo={args.logo}
|
||||
onClick={args.onClick}
|
||||
className={args.className}
|
||||
contentSize={args.contentSize}
|
||||
/>
|
||||
</StyledTabContainer>
|
||||
<TabContainer>
|
||||
<TabButton {...args} />
|
||||
</TabContainer>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -196,22 +137,9 @@ export const WithBothIcons: Story = {
|
||||
RightIcon: IconChevronDown,
|
||||
},
|
||||
render: (args) => (
|
||||
<StyledTabContainer>
|
||||
<TabButton
|
||||
id={args.id}
|
||||
title={args.title}
|
||||
LeftIcon={args.LeftIcon}
|
||||
RightIcon={args.RightIcon}
|
||||
active={args.active}
|
||||
disabled={args.disabled}
|
||||
pill={args.pill}
|
||||
to={args.to}
|
||||
logo={args.logo}
|
||||
onClick={args.onClick}
|
||||
className={args.className}
|
||||
contentSize={args.contentSize}
|
||||
/>
|
||||
</StyledTabContainer>
|
||||
<TabContainer>
|
||||
<TabButton {...args} />
|
||||
</TabContainer>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -222,22 +150,9 @@ export const AsLink: Story = {
|
||||
to: '/profile',
|
||||
},
|
||||
render: (args) => (
|
||||
<StyledTabContainer>
|
||||
<TabButton
|
||||
id={args.id}
|
||||
title={args.title}
|
||||
LeftIcon={args.LeftIcon}
|
||||
RightIcon={args.RightIcon}
|
||||
active={args.active}
|
||||
disabled={args.disabled}
|
||||
pill={args.pill}
|
||||
to={args.to}
|
||||
logo={args.logo}
|
||||
onClick={args.onClick}
|
||||
className={args.className}
|
||||
contentSize={args.contentSize}
|
||||
/>
|
||||
</StyledTabContainer>
|
||||
<TabContainer>
|
||||
<TabButton {...args} />
|
||||
</TabContainer>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -248,22 +163,9 @@ export const SmallContent: Story = {
|
||||
contentSize: 'sm',
|
||||
},
|
||||
render: (args) => (
|
||||
<StyledTabContainer>
|
||||
<TabButton
|
||||
id={args.id}
|
||||
title={args.title}
|
||||
LeftIcon={args.LeftIcon}
|
||||
RightIcon={args.RightIcon}
|
||||
active={args.active}
|
||||
disabled={args.disabled}
|
||||
pill={args.pill}
|
||||
to={args.to}
|
||||
logo={args.logo}
|
||||
onClick={args.onClick}
|
||||
className={args.className}
|
||||
contentSize={args.contentSize}
|
||||
/>
|
||||
</StyledTabContainer>
|
||||
<TabContainer>
|
||||
<TabButton {...args} />
|
||||
</TabContainer>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -274,22 +176,9 @@ export const MediumContent: Story = {
|
||||
contentSize: 'md',
|
||||
},
|
||||
render: (args) => (
|
||||
<StyledTabContainer>
|
||||
<TabButton
|
||||
id={args.id}
|
||||
title={args.title}
|
||||
LeftIcon={args.LeftIcon}
|
||||
RightIcon={args.RightIcon}
|
||||
active={args.active}
|
||||
disabled={args.disabled}
|
||||
pill={args.pill}
|
||||
to={args.to}
|
||||
logo={args.logo}
|
||||
onClick={args.onClick}
|
||||
className={args.className}
|
||||
contentSize={args.contentSize}
|
||||
/>
|
||||
</StyledTabContainer>
|
||||
<TabContainer>
|
||||
<TabButton {...args} />
|
||||
</TabContainer>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -305,22 +194,9 @@ export const Catalog: CatalogStory<Story, typeof TabButton> = {
|
||||
to: { control: false },
|
||||
},
|
||||
render: (args) => (
|
||||
<StyledTabContainer>
|
||||
<TabButton
|
||||
id={args.id}
|
||||
title={args.title}
|
||||
LeftIcon={args.LeftIcon}
|
||||
RightIcon={args.RightIcon}
|
||||
active={args.active}
|
||||
disabled={args.disabled}
|
||||
pill={args.pill}
|
||||
to={args.to}
|
||||
logo={args.logo}
|
||||
onClick={args.onClick}
|
||||
className={args.className}
|
||||
contentSize={args.contentSize}
|
||||
/>
|
||||
</StyledTabContainer>
|
||||
<TabContainer>
|
||||
<TabButton {...args} />
|
||||
</TabContainer>
|
||||
),
|
||||
parameters: {
|
||||
pseudo: { hover: ['.hover'], active: ['.active'] },
|
||||
|
||||
Reference in New Issue
Block a user