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:
2024-02-09 23:16:00 +04:00
committed by doryan
parent 4778ce7e37
commit dbb19057ac
15 changed files with 148 additions and 45 deletions

View File

@@ -1,6 +1,6 @@
'use client';
import { bool, string } from 'prop-types';
import { bool } from 'prop-types';
import { RippleEffect } from '../../ripple/ripple-effect';
import { ButtonLayoutProps } from './button-layout.types';
import useRippleEffect from '../../ripple/hooks/useRippleEffect';
@@ -37,6 +37,5 @@ export const ButtonLayout = forwardRef<HTMLButtonElement, ButtonLayoutProps>(
);
ButtonLayout.propTypes = {
children: string,
centralRipple: bool,
};

View File

@@ -1,10 +1,10 @@
'use client';
import { forwardRef } from 'react';
import { Icon } from '../../components';
import { ButtonProps } from './button.types';
import { bool, oneOf, string } from 'prop-types';
import { ButtonLayout } from '../button-layout/button-layout';
import { IconWrapper } from '../../icon/icon-wrapper';
/**
* Button component
@@ -14,10 +14,11 @@ import { ButtonLayout } from '../button-layout/button-layout';
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(
{
icon,
icon = undefined,
className = '',
disabled = false,
variant = 'filled',
iconPlace = 'left',
centralRipple = false,
...props
},
@@ -30,8 +31,9 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
disabled={disabled}
ref={ref}
>
{icon ? <Icon iconSize={20}>{icon}</Icon> : <></>}
<span className={'label-large'}>{props.children}</span>
<IconWrapper icon={icon} iconPlace={iconPlace}>
<span className={'label-large'}>{props.children}</span>
</IconWrapper>
</ButtonLayout>
),
);

View File

@@ -6,6 +6,7 @@ export interface ButtonMainProps {
children?: string;
disabled?: boolean;
variant?: 'filled' | 'outlined' | 'elevated' | 'tonal' | 'text';
iconPlace?: 'left' | 'right';
}
export type ButtonProps = RipplePropsForComponents<HTMLButtonElement> &

View File

@@ -1,29 +1,51 @@
import React, { cloneElement, forwardRef, ReactElement } from 'react';
import { SegmentedButtonProps } from './segmented-buttons.types';
import React, { forwardRef } from 'react';
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<
HTMLDivElement,
SegmentedButtonProps
SegmentedButtonsProps
>(({ children, ...props }, ref) => {
if (children.length <= 1) {
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 (
<div
className={`m3 m3-segmented-buttons ${props.className ?? ''}`.trimEnd()}
ref={ref}
>
{buttons}
{children}
</div>
);
});

View File

@@ -1,8 +1,14 @@
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>[];
}
export type SegmentedButtonProps = SegmentedButton &
export type SegmentedButtonsProps = SegmentedButtons &
HTMLAttributes<HTMLDivElement>;

View File

@@ -3,9 +3,9 @@ export { Icon } from './icon/icon';
export { Badge } from './badge/badge';
export { Ripple } from './ripple/ripple';
export { Divider } from './divider/divider';
export { FAB } from './button-components/fab/fab';
export { Container } from './container/container';
export { RippleEffect } from './ripple/ripple-effect';
export { FAB } from './button-components/fab/fab';
export { Radio } from './input-components/radio/radio';
export { Switch } from './input-components/switch/switch';
export { Button } from './button-components/button/button';

View File

@@ -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>}
</>
);
}

View File

@@ -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;
svgSize?: number;
fillIcon?: 0 | 1;
iconSize?: number;
opticalSize?: number;
children?: string | undefined;
type?: 'outlined' | 'rounded' | 'sharp';
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;
};

View File

@@ -30,7 +30,7 @@ export interface RipplePropsForComponents<T> extends HTMLAttributes<T> {
}
export interface RippleAreaProps extends HTMLAttributes<HTMLElement> {
callback: Dispatch<SetStateAction<boolean>>;
callback?: Dispatch<SetStateAction<boolean>>;
central?: boolean;
}