ADDED: Icon wrapper for more elegant way to placement icon in buttons
TODO: Add styles for selected segmented button DONE: Base styles for segmented buttons
This commit is contained in:
parent
7d79badf8b
commit
b0275dec80
26
app/page.tsx
26
app/page.tsx
|
@ -5,13 +5,11 @@ import { CardBody } from '../src/primitive-components/card/card-body';
|
||||||
import { CardMedia } from '../src/primitive-components/card/card-media';
|
import { CardMedia } from '../src/primitive-components/card/card-media';
|
||||||
import { CardFooter } from '../src/primitive-components/card/card-footer';
|
import { CardFooter } from '../src/primitive-components/card/card-footer';
|
||||||
import { CardHeader } from '../src/primitive-components/card/card-header';
|
import { CardHeader } from '../src/primitive-components/card/card-header';
|
||||||
|
import { SegmentedButtons } from '../src/primitive-components/components';
|
||||||
|
import { Typography } from '../src/primitive-components/typography/typography';
|
||||||
import { CardActionArea } from '../src/primitive-components/card/card-action-area';
|
import { CardActionArea } from '../src/primitive-components/card/card-action-area';
|
||||||
import { Button } from '../src/primitive-components/button-components/button/button';
|
import { Button } from '../src/primitive-components/button-components/button/button';
|
||||||
import { Typography } from '../src/primitive-components/typography/typography';
|
import { SegmentButton } from '../src/primitive-components/button-components/segmented-buttons/segmented-buttons';
|
||||||
import {
|
|
||||||
ButtonLayout,
|
|
||||||
SegmentedButtons,
|
|
||||||
} from '../src/primitive-components/components';
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
|
@ -50,11 +48,21 @@ export default function Page() {
|
||||||
</CardActionArea>
|
</CardActionArea>
|
||||||
<CardFooter>
|
<CardFooter>
|
||||||
<div className={'flex flex-row gap-3'}>
|
<div className={'flex flex-row gap-3'}>
|
||||||
<Button>label</Button>
|
<Button icon={'add'}>Label 1</Button>
|
||||||
|
<Button icon={'add'} iconPlace={'right'}>
|
||||||
|
Label 2
|
||||||
|
</Button>
|
||||||
<SegmentedButtons>
|
<SegmentedButtons>
|
||||||
<ButtonLayout>Label 1</ButtonLayout>
|
<SegmentButton icon={'add'}>
|
||||||
<ButtonLayout>Label 2</ButtonLayout>
|
Label 1
|
||||||
<ButtonLayout>Label 3</ButtonLayout>
|
</SegmentButton>
|
||||||
|
<SegmentButton
|
||||||
|
icon={'add'}
|
||||||
|
iconPlace={'right'}
|
||||||
|
>
|
||||||
|
Label 2
|
||||||
|
</SegmentButton>
|
||||||
|
<SegmentButton>Label 3</SegmentButton>
|
||||||
</SegmentedButtons>
|
</SegmentedButtons>
|
||||||
</div>
|
</div>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { bool, string } from 'prop-types';
|
import { bool } from 'prop-types';
|
||||||
import { RippleEffect } from '../../ripple/ripple-effect';
|
import { RippleEffect } from '../../ripple/ripple-effect';
|
||||||
import { ButtonLayoutProps } from './button-layout.types';
|
import { ButtonLayoutProps } from './button-layout.types';
|
||||||
import useRippleEffect from '../../ripple/hooks/useRippleEffect';
|
import useRippleEffect from '../../ripple/hooks/useRippleEffect';
|
||||||
|
@ -37,6 +37,5 @@ export const ButtonLayout = forwardRef<HTMLButtonElement, ButtonLayoutProps>(
|
||||||
);
|
);
|
||||||
|
|
||||||
ButtonLayout.propTypes = {
|
ButtonLayout.propTypes = {
|
||||||
children: string,
|
|
||||||
centralRipple: bool,
|
centralRipple: bool,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
import { Icon } from '../../components';
|
|
||||||
import { ButtonProps } from './button.types';
|
import { ButtonProps } from './button.types';
|
||||||
import { bool, oneOf, string } from 'prop-types';
|
import { bool, oneOf, string } from 'prop-types';
|
||||||
import { ButtonLayout } from '../button-layout/button-layout';
|
import { ButtonLayout } from '../button-layout/button-layout';
|
||||||
|
import { IconWrapper } from '../../icon/icon-wrapper';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Button component
|
* Button component
|
||||||
|
@ -14,10 +14,11 @@ import { ButtonLayout } from '../button-layout/button-layout';
|
||||||
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
icon,
|
icon = undefined,
|
||||||
className = '',
|
className = '',
|
||||||
disabled = false,
|
disabled = false,
|
||||||
variant = 'filled',
|
variant = 'filled',
|
||||||
|
iconPlace = 'left',
|
||||||
centralRipple = false,
|
centralRipple = false,
|
||||||
...props
|
...props
|
||||||
},
|
},
|
||||||
|
@ -30,8 +31,9 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
{icon ? <Icon iconSize={20}>{icon}</Icon> : <></>}
|
<IconWrapper icon={icon} iconPlace={iconPlace}>
|
||||||
<span className={'label-large'}>{props.children}</span>
|
<span className={'label-large'}>{props.children}</span>
|
||||||
|
</IconWrapper>
|
||||||
</ButtonLayout>
|
</ButtonLayout>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,6 +6,7 @@ export interface ButtonMainProps {
|
||||||
children?: string;
|
children?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
variant?: 'filled' | 'outlined' | 'elevated' | 'tonal' | 'text';
|
variant?: 'filled' | 'outlined' | 'elevated' | 'tonal' | 'text';
|
||||||
|
iconPlace?: 'left' | 'right';
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ButtonProps = RipplePropsForComponents<HTMLButtonElement> &
|
export type ButtonProps = RipplePropsForComponents<HTMLButtonElement> &
|
||||||
|
|
|
@ -1,29 +1,51 @@
|
||||||
import React, { cloneElement, forwardRef, ReactElement } from 'react';
|
import React, { forwardRef } from 'react';
|
||||||
import { SegmentedButtonProps } from './segmented-buttons.types';
|
import {
|
||||||
|
SegmentedButton,
|
||||||
|
SegmentedButtonsProps,
|
||||||
|
} from './segmented-buttons.types';
|
||||||
|
import { string } from 'prop-types';
|
||||||
|
import { ButtonLayout } from '../../components';
|
||||||
|
import { ButtonLayoutProps } from '../button-layout/button-layout.types';
|
||||||
|
import { IconWrapper } from '../../icon/icon-wrapper';
|
||||||
|
|
||||||
|
export const SegmentButton = forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
ButtonLayoutProps & SegmentedButton
|
||||||
|
>(({ centralRipple = false, iconPlace = 'left', icon, ...props }, ref) => {
|
||||||
|
const classes = `m3-button-segment ${props.className ?? ''}`.trimEnd();
|
||||||
|
return (
|
||||||
|
<ButtonLayout
|
||||||
|
{...props}
|
||||||
|
centralRipple={centralRipple}
|
||||||
|
className={classes}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
<IconWrapper icon={icon} iconPlace={iconPlace}>
|
||||||
|
{props.children}
|
||||||
|
</IconWrapper>
|
||||||
|
<span className={'m3 m3-button-segment-state-layer'} />
|
||||||
|
</ButtonLayout>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
SegmentButton.propTypes = {
|
||||||
|
children: string,
|
||||||
|
};
|
||||||
|
|
||||||
export const SegmentedButtons = forwardRef<
|
export const SegmentedButtons = forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
SegmentedButtonProps
|
SegmentedButtonsProps
|
||||||
>(({ children, ...props }, ref) => {
|
>(({ children, ...props }, ref) => {
|
||||||
if (children.length <= 1) {
|
if (children.length <= 1) {
|
||||||
throw 'You must build segmented button with 2 or more buttton';
|
throw 'You must build segmented button with 2 or more buttton';
|
||||||
}
|
}
|
||||||
|
|
||||||
const buttons = children.map((button: ReactElement, index) => {
|
|
||||||
const classes =
|
|
||||||
`m3-button-segment ${button.props.className ?? ''}`.trimEnd();
|
|
||||||
return cloneElement(button, {
|
|
||||||
className: classes,
|
|
||||||
key: index,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`m3 m3-segmented-buttons ${props.className ?? ''}`.trimEnd()}
|
className={`m3 m3-segmented-buttons ${props.className ?? ''}`.trimEnd()}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
{buttons}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
import { HTMLAttributes, ReactElement } from 'react';
|
import { HTMLAttributes, ReactElement } from 'react';
|
||||||
|
import { IconWrapperProps } from '../../icon/icon.types';
|
||||||
|
|
||||||
export interface SegmentedButton {
|
export type SegmentedButton = IconWrapperProps & {
|
||||||
|
icon?: string;
|
||||||
|
centralRipple?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface SegmentedButtons {
|
||||||
children?: ReactElement<HTMLButtonElement>[];
|
children?: ReactElement<HTMLButtonElement>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SegmentedButtonProps = SegmentedButton &
|
export type SegmentedButtonsProps = SegmentedButtons &
|
||||||
HTMLAttributes<HTMLDivElement>;
|
HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
|
@ -3,9 +3,9 @@ export { Icon } from './icon/icon';
|
||||||
export { Badge } from './badge/badge';
|
export { Badge } from './badge/badge';
|
||||||
export { Ripple } from './ripple/ripple';
|
export { Ripple } from './ripple/ripple';
|
||||||
export { Divider } from './divider/divider';
|
export { Divider } from './divider/divider';
|
||||||
|
export { FAB } from './button-components/fab/fab';
|
||||||
export { Container } from './container/container';
|
export { Container } from './container/container';
|
||||||
export { RippleEffect } from './ripple/ripple-effect';
|
export { RippleEffect } from './ripple/ripple-effect';
|
||||||
export { FAB } from './button-components/fab/fab';
|
|
||||||
export { Radio } from './input-components/radio/radio';
|
export { Radio } from './input-components/radio/radio';
|
||||||
export { Switch } from './input-components/switch/switch';
|
export { Switch } from './input-components/switch/switch';
|
||||||
export { Button } from './button-components/button/button';
|
export { Button } from './button-components/button/button';
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Icon } from './icon';
|
||||||
|
import { IconWrapperProps } from './icon.types';
|
||||||
|
|
||||||
|
export function IconWrapper({
|
||||||
|
children,
|
||||||
|
icon,
|
||||||
|
iconPlace,
|
||||||
|
...props
|
||||||
|
}: IconWrapperProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{icon && iconPlace === 'left' && <Icon {...props}>{icon}</Icon>}
|
||||||
|
{children}
|
||||||
|
{icon && iconPlace === 'right' && <Icon {...props}>{icon}</Icon>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,12 +1,26 @@
|
||||||
import { SVGProps } from 'react';
|
import { PropsWithChildren, SVGProps } from 'react';
|
||||||
|
|
||||||
export interface IconProps extends SVGProps<SVGSVGElement> {
|
export interface IconPlacement {
|
||||||
|
iconPlace?: 'left' | 'right';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GeneralIconProps {
|
||||||
grade?: number;
|
grade?: number;
|
||||||
svgSize?: number;
|
svgSize?: number;
|
||||||
fillIcon?: 0 | 1;
|
fillIcon?: 0 | 1;
|
||||||
iconSize?: number;
|
iconSize?: number;
|
||||||
opticalSize?: number;
|
opticalSize?: number;
|
||||||
children?: string | undefined;
|
|
||||||
type?: 'outlined' | 'rounded' | 'sharp';
|
type?: 'outlined' | 'rounded' | 'sharp';
|
||||||
weight?: 100 | 200 | 300 | 400 | 500 | 600 | 700;
|
weight?: 100 | 200 | 300 | 400 | 500 | 600 | 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type IconWrapperProps = IconPlacement &
|
||||||
|
GeneralIconProps &
|
||||||
|
PropsWithChildren & {
|
||||||
|
icon?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IconProps = SVGProps<SVGSVGElement> &
|
||||||
|
GeneralIconProps & {
|
||||||
|
children?: string | undefined;
|
||||||
|
};
|
||||||
|
|
|
@ -30,7 +30,7 @@ export interface RipplePropsForComponents<T> extends HTMLAttributes<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RippleAreaProps extends HTMLAttributes<HTMLElement> {
|
export interface RippleAreaProps extends HTMLAttributes<HTMLElement> {
|
||||||
callback: Dispatch<SetStateAction<boolean>>;
|
callback?: Dispatch<SetStateAction<boolean>>;
|
||||||
central?: boolean;
|
central?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
button:not(.m3-fab, .m3-icon-button)
|
button:not(.m3-fab, .m3-icon-button)
|
||||||
width: min-content
|
width: min-content
|
||||||
height: min-content
|
height: min-content
|
||||||
|
max-height: 40px
|
||||||
box-sizing: border-box
|
box-sizing: border-box
|
||||||
|
white-space: nowrap
|
||||||
font-size: var(--md-sys-typescale-label-large-font-size)
|
font-size: var(--md-sys-typescale-label-large-font-size)
|
||||||
font-weight: var(--md-sys-typescale-label-large-font-weight)
|
font-weight: var(--md-sys-typescale-label-large-font-weight)
|
||||||
line-height: var(--md-sys-typescale-label-large-line-height)
|
line-height: var(--md-sys-typescale-label-large-line-height)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
div.m3.m3-segmented-buttons
|
div.m3.m3-segmented-buttons
|
||||||
display: flex
|
display: flex
|
||||||
flex-direction: row
|
flex-direction: row
|
||||||
outline-offset: -1px
|
|
||||||
width: min-content
|
width: min-content
|
||||||
border-radius: 20px
|
border-radius: 20px
|
||||||
|
|
||||||
|
@ -21,3 +20,23 @@ div.m3.m3-segmented-buttons
|
||||||
|
|
||||||
&:last-child
|
&:last-child
|
||||||
border-radius: 0 20px 20px 0
|
border-radius: 0 20px 20px 0
|
||||||
|
|
||||||
|
& > span.m3.m3-button-segment-state-layer
|
||||||
|
position: absolute
|
||||||
|
width: 100%
|
||||||
|
height: 100%
|
||||||
|
|
||||||
|
& > span.m3.m3-button-segment-state-layer, span.m3.m3-ripple-domain
|
||||||
|
transition: .2s cubic-bezier(0.2, 0, 0, 1)
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
& > span.m3.m3-button-segment-state-layer
|
||||||
|
background-color: color-mix(in srgb, var(--md-sys-color-on-secondary-container) 8%, transparent)
|
||||||
|
|
||||||
|
&:is(&:active, &:focus-visible)
|
||||||
|
& > span.m3.m3-button-segment-state-layer
|
||||||
|
background-color: color-mix(in srgb, var(--md-sys-color-on-secondary-container) 12%, transparent)
|
||||||
|
|
||||||
|
&:active
|
||||||
|
& > span.m3.m3-ripple-domain > span.m3.ripple
|
||||||
|
background-color: color-mix(in srgb, var(--md-sys-color-on-secondary-container) 12%, transparent)
|
||||||
|
|
|
@ -80,8 +80,5 @@ div.m3.m3-card-action-area
|
||||||
& > span.m3.m3-card-state-layer
|
& > span.m3.m3-card-state-layer
|
||||||
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent)
|
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent)
|
||||||
|
|
||||||
& > span.m3.m3-card-state-layer
|
|
||||||
background-color: transparent
|
|
||||||
|
|
||||||
& > span.m3.m3-ripple-domain > .m3.ripple
|
& > span.m3.m3-ripple-domain > .m3.ripple
|
||||||
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent)
|
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent)
|
||||||
|
|
|
@ -71,9 +71,6 @@ div.m3.m3-card-action-area:active.m3-card-elevated {
|
||||||
div.m3.m3-card-action-area:active:not(div.m3.m3-card-action-area:active:has(span.m3.m3-ripple-domain)) > span.m3.m3-card-state-layer {
|
div.m3.m3-card-action-area:active:not(div.m3.m3-card-action-area:active:has(span.m3.m3-ripple-domain)) > span.m3.m3-card-state-layer {
|
||||||
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent);
|
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent);
|
||||||
}
|
}
|
||||||
div.m3.m3-card-action-area:active > span.m3.m3-card-state-layer {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
div.m3.m3-card-action-area:active > span.m3.m3-ripple-domain > .m3.ripple {
|
div.m3.m3-card-action-area:active > span.m3.m3-ripple-domain > .m3.ripple {
|
||||||
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent);
|
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent);
|
||||||
}
|
}
|
||||||
|
@ -449,7 +446,9 @@ button.m3.m3-fab:focus-visible.tertiary::before {
|
||||||
button:not(.m3-fab, .m3-icon-button) {
|
button:not(.m3-fab, .m3-icon-button) {
|
||||||
width: min-content;
|
width: min-content;
|
||||||
height: min-content;
|
height: min-content;
|
||||||
|
max-height: 40px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
white-space: nowrap;
|
||||||
font-size: var(--md-sys-typescale-label-large-font-size);
|
font-size: var(--md-sys-typescale-label-large-font-size);
|
||||||
font-weight: var(--md-sys-typescale-label-large-font-weight);
|
font-weight: var(--md-sys-typescale-label-large-font-weight);
|
||||||
line-height: var(--md-sys-typescale-label-large-line-height);
|
line-height: var(--md-sys-typescale-label-large-line-height);
|
||||||
|
@ -715,7 +714,6 @@ button.m3.m3-icon-button:focus-visible:not(:disabled).tonal.toggled::before {
|
||||||
div.m3.m3-segmented-buttons {
|
div.m3.m3-segmented-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
outline-offset: -1px;
|
|
||||||
width: min-content;
|
width: min-content;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
}
|
}
|
||||||
|
@ -736,6 +734,23 @@ div.m3.m3-segmented-buttons > button.m3.m3-button-segment:first-child {
|
||||||
div.m3.m3-segmented-buttons > button.m3.m3-button-segment:last-child {
|
div.m3.m3-segmented-buttons > button.m3.m3-button-segment:last-child {
|
||||||
border-radius: 0 20px 20px 0;
|
border-radius: 0 20px 20px 0;
|
||||||
}
|
}
|
||||||
|
div.m3.m3-segmented-buttons > button.m3.m3-button-segment > span.m3.m3-button-segment-state-layer {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
div.m3.m3-segmented-buttons > button.m3.m3-button-segment > span.m3.m3-button-segment-state-layer, div.m3.m3-segmented-buttons > button.m3.m3-button-segment span.m3.m3-ripple-domain {
|
||||||
|
transition: 0.2s cubic-bezier(0.2, 0, 0, 1);
|
||||||
|
}
|
||||||
|
div.m3.m3-segmented-buttons > button.m3.m3-button-segment:hover > span.m3.m3-button-segment-state-layer {
|
||||||
|
background-color: color-mix(in srgb, var(--md-sys-color-on-secondary-container) 8%, transparent);
|
||||||
|
}
|
||||||
|
div.m3.m3-segmented-buttons > button.m3.m3-button-segment:is(div.m3.m3-segmented-buttons > button.m3.m3-button-segment:active, div.m3.m3-segmented-buttons > button.m3.m3-button-segment:focus-visible) > span.m3.m3-button-segment-state-layer {
|
||||||
|
background-color: color-mix(in srgb, var(--md-sys-color-on-secondary-container) 12%, transparent);
|
||||||
|
}
|
||||||
|
div.m3.m3-segmented-buttons > button.m3.m3-button-segment:active > span.m3.m3-ripple-domain > span.m3.ripple {
|
||||||
|
background-color: color-mix(in srgb, var(--md-sys-color-on-secondary-container) 12%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
div.m3.m3-radio {
|
div.m3.m3-radio {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue