Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/mobile-app/scripts/utils/routes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,11 @@ export const routes = [
key: 'Frontier',
getComponent: () => require('@coinbase/cds-mobile/system/__stories__/Frontier.stories').default,
},
{
key: 'GradientBox',
getComponent: () =>
require('@coinbase/cds-mobile/layout/__stories__/GradientBox.stories').default,
},
{
key: 'Group',
getComponent: () => require('@coinbase/cds-mobile/layout/__stories__/Group.stories').default,
Expand Down
5 changes: 5 additions & 0 deletions apps/mobile-app/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,11 @@ export const routes = [
key: 'Frontier',
getComponent: () => require('@coinbase/cds-mobile/system/__stories__/Frontier.stories').default,
},
{
key: 'GradientBox',
getComponent: () =>
require('@coinbase/cds-mobile/layout/__stories__/GradientBox.stories').default,
},
{
key: 'Group',
getComponent: () => require('@coinbase/cds-mobile/layout/__stories__/Group.stories').default,
Expand Down
13 changes: 13 additions & 0 deletions packages/common/src/core/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,14 @@ export namespace ThemeVarsDefault {
1: void;
2: void;
}

export interface Gradient {
primary: void;
positive: void;
negative: void;
brand: void;
premium: void;
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is subject to change based on design decision

}

declare module '@coinbase/cds-common/core/theme' {
Expand Down Expand Up @@ -251,6 +259,7 @@ declare module '@coinbase/cds-common/core/theme' {
export interface Shadow {}
export interface ControlSize {}
export interface Elevation {}
export interface Gradient {}
}
}

Expand Down Expand Up @@ -328,4 +337,8 @@ export namespace ThemeVars {
export type Elevation = Prettify<
keyof ThemeVarsDefault.Elevation | keyof ThemeVarsExtended.Elevation
>;

export type Gradient = Prettify<
keyof ThemeVarsDefault.Gradient | keyof ThemeVarsExtended.Gradient
>;
}
10 changes: 10 additions & 0 deletions packages/common/src/tokens/button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ type ButtonVariantStyles = {
type ButtonVariantConfig = Record<ButtonVariant, ButtonVariantStyles>;

export const variants = {
gradient: {
color: 'fgInverse',
background: 'transparent',
borderColor: 'transparent',
},
primary: {
color: 'fgInverse',
background: 'bgPrimary',
Expand Down Expand Up @@ -43,6 +48,11 @@ export const variants = {
} as const satisfies ButtonVariantConfig;

export const transparentVariants = {
gradient: {
color: 'fgInverse',
background: 'transparent',
borderColor: 'transparent',
},
primary: {
color: 'fgPrimary',
background: 'bg',
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/types/ButtonBaseProps.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export type ButtonVariant =
| 'primary'
| 'gradient'
| 'secondary'
| 'tertiary'
| 'positive'
Expand Down
4 changes: 4 additions & 0 deletions packages/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@
"types": "./dts/controls/index.d.ts",
"default": "./esm/controls/index.js"
},
"./gradients": {
"types": "./dts/gradients/index.d.ts",
"default": "./esm/gradients/index.js"
},
"./dates": {
"types": "./dts/dates/index.d.ts",
"default": "./esm/dates/index.js"
Expand Down
36 changes: 34 additions & 2 deletions packages/mobile/src/buttons/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,18 @@ export const styles = StyleSheet.create({

export type ButtonBaseProps = SharedProps &
Pick<SharedAccessibilityProps, 'accessibilityLabel'> &
PressableBaseProps & {
Omit<PressableBaseProps, 'gradient' | 'gradientConfig' | 'gradientNode'> & {
/**
* Toggle design and visual variants.
*
* For gradient buttons, set `variant="gradient"` along with one of:
* - `gradient` prop with a theme preset name (e.g., "brand", "primary")
* - `gradientConfig` prop with a custom config object (e.g., `{ colors: ['#0052FF', '#7B3FE4'], angle: 90 }`)
* - `blendStyles.backgroundGradient` for state-based gradients (hover/pressed/disabled)
* - `gradientNode` prop with a custom node to render (e.g., `<RadialGradientFill />`)
*
* Note: gradient/gradientConfig props are ignored unless variant="gradient" is set.
*
* @default primary
*/
variant?: ButtonVariant;
Expand Down Expand Up @@ -84,6 +93,21 @@ export type ButtonBaseProps = SharedProps &
* @default 1
*/
numberOfLines?: number;
/**
* Theme gradient preset name. Only applied when `variant="gradient"`.
* @example gradient="brand"
*/
gradient?: PressableBaseProps['gradient'];
/**
* Custom gradient configuration. Only applied when `variant="gradient"`.
* @example gradientConfig={{ colors: ['#0052FF', '#7B3FE4'], angle: 90 }}
*/
gradientConfig?: PressableBaseProps['gradientConfig'];
/**
* Custom gradient node to render. Only applied when `variant="gradient"`.
* @example gradientNode={<RadialGradientFill colors={['#0052FF', '#7B3FE4']} />}
*/
gradientNode?: PressableBaseProps['gradientNode'];
};

export type ButtonProps = ButtonBaseProps;
Expand Down Expand Up @@ -117,17 +141,22 @@ export const Button = memo(
wrapperStyles,
feedback = compact ? 'light' : 'normal',
borderColor,
borderWidth = 100,
// TO DO: This is a hack to fix the anti-aliasing issue with gradients.
borderWidth = variant === 'gradient' ? 0 : 100,
borderRadius = compact ? 700 : 900,
accessibilityLabel,
accessibilityHint,
gradient,
gradientConfig,
gradientNode,
...props
}: ButtonProps,
ref: React.ForwardedRef<View>,
) {
const theme = useTheme();
const iconSize = compact ? 's' : 'm';
const hasIcon = Boolean(startIcon || endIcon);
const isGradientVariant = variant === 'gradient';

const variantMap = transparent ? transparentVariants : variants;

Expand Down Expand Up @@ -191,6 +220,9 @@ export const Button = memo(
borderRadius={borderRadius}
borderWidth={borderWidth}
feedback={feedback}
gradient={isGradientVariant ? gradient : undefined}
gradientConfig={isGradientVariant ? gradientConfig : undefined}
gradientNode={isGradientVariant ? gradientNode : undefined}
loading={loading}
marginEnd={marginEnd}
marginStart={marginStart}
Expand Down
23 changes: 20 additions & 3 deletions packages/mobile/src/buttons/IconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,18 @@ import { Pressable, type PressableBaseProps } from '../system/Pressable';
import type { ButtonBaseProps } from './Button';

export type IconButtonBaseProps = SharedProps &
Omit<PressableBaseProps, 'children'> &
Pick<ButtonBaseProps, 'disabled' | 'transparent' | 'compact' | 'flush' | 'loading'> & {
Omit<PressableBaseProps, 'children' | 'gradient' | 'gradientConfig' | 'gradientNode'> &
Pick<
ButtonBaseProps,
| 'disabled'
| 'transparent'
| 'compact'
| 'flush'
| 'loading'
| 'gradient'
| 'gradientConfig'
| 'gradientNode'
> & {
/** Name of the icon, as defined in Figma. */
name: IconName;
/** Whether the icon is active */
Expand All @@ -36,18 +46,22 @@ export const IconButton = memo(function IconButton({
background,
color,
borderColor,
borderWidth = 100,
borderWidth = variant === 'gradient' ? undefined : 100,
borderRadius = 1000,
feedback = compact ? 'light' : 'normal',
flush,
loading,
style,
accessibilityHint,
accessibilityLabel,
gradient,
gradientConfig,
gradientNode,
...props
}: IconButtonProps) {
const theme = useTheme();
const iconSize = compact ? 's' : 'm';
const isGradientVariant = variant === 'gradient';

const variantMap = transparent ? transparentVariants : variants;
const variantStyle = variantMap[variant];
Expand Down Expand Up @@ -88,6 +102,9 @@ export const IconButton = memo(function IconButton({
borderRadius={borderRadius}
borderWidth={borderWidth}
feedback={feedback}
gradient={isGradientVariant ? gradient : undefined}
gradientConfig={isGradientVariant ? gradientConfig : undefined}
gradientNode={isGradientVariant ? gradientNode : undefined}
loading={loading}
marginEnd={marginEnd}
marginStart={marginStart}
Expand Down
Loading