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
37 changes: 37 additions & 0 deletions packages/react/src/components/OptionSwatch/OptionSwatch.api.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Meta } from '@storybook/addon-docs';
import LinkTo from '@storybook/addon-links/react';
import { TableInterface } from '~storybook/components/TableInterface';

<Meta title="Components API/OptionSwatch" />

# OptionSwatch API

```js
import { OptionSwatch } from '@esfront/react';
```

## Component name

The name `ESOptionSwatch` can be used when providing default props or style overrides in the theme.

## Props

<TableInterface name="OptionSwatchProps" variant="props" />

<br />

## CSS

<TableInterface name="OptionSwatchClasses" variant="css" />

<br />

## Demos

<ul>
<li>
<LinkTo kind="components-OptionSwatch" story="demo">
<code>OptionSwatch</code>
</LinkTo>
</li>
</ul>
21 changes: 21 additions & 0 deletions packages/react/src/components/OptionSwatch/OptionSwatch.classes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { generateUtilityClass, generateUtilityClasses } from '@mui/material';

export type OptionSwatchClasses = {
/** Class name applied to the root element. */
root: string;
/** State class applied to the root element if `checked={true}`. */
checked: string;
/** State class applied to the root element if `disabled={true}`. */
disabled: string;
};
export type OptionSwatchClassKey = keyof OptionSwatchClasses;

export function getOptionSwatchUtilityClass(slot: string): string {
return generateUtilityClass('ESOptionSwatch', slot);
}

export const optionSwatchClasses: OptionSwatchClasses = generateUtilityClasses('ESOptionSwatch', [
'root',
'checked',
'disabled',
]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { ComponentProps } from 'react';

import { Meta, StoryObj } from '@storybook/react';

import { OptionSwatch } from './OptionSwatch';

type Args = ComponentProps<typeof OptionSwatch>;

const meta: Meta<Args> = {
tags: ['autodocs'],
component: OptionSwatch,
parameters: {
references: ['OptionSwatch'],
},
argTypes: {
inputProps: {
table: {
disable: true,
},
},
inputRef: {
table: {
disable: true,
},
},
value: {
table: {
disable: true,
},
},
},
};

export default meta;

type Story = StoryObj<Args>;

export const Demo: Story = {
render: (args) => {
return <OptionSwatch {...args} />;
},
};
154 changes: 154 additions & 0 deletions packages/react/src/components/OptionSwatch/OptionSwatch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import * as React from 'react';

import { OptionSwatchProps } from './OptionSwatch.types';

import clsx from 'clsx';
import { getOptionSwatchUtilityClass, optionSwatchClasses } from './OptionSwatch.classes';

import { styled } from '@mui/material/styles';
import { useDefaultProps } from '@mui/system/DefaultPropsProvider';
import { createChainedFunction } from '@mui/material';
import composeClasses from '@mui/utils/composeClasses';

import { ButtonBase } from '../ButtonBase';
import { useRadioGroup } from '../RadioGroup';

type OptionSwatchOwnerState = {
classes?: OptionSwatchProps['classes'];
checked?: OptionSwatchProps['checked'];
disabled?: OptionSwatchProps['disabled'];
};

const useUtilityClasses = (ownerState: OptionSwatchOwnerState) => {
const { classes, checked, disabled } = ownerState;

const slots = {
root: ['root', checked && 'checked', disabled && 'disabled'],
};

const composedClasses = composeClasses(slots, getOptionSwatchUtilityClass, classes);

return {
...classes,
...composedClasses,
};
};

const OptionSwatchRoot = styled(ButtonBase, {
name: 'ESOptionSwatch',
slot: 'Root',
overridesResolver: (props, styles) => {
const { ownerState } = props;

return [styles.root, ownerState.checked && styles.checked, ownerState.disabled && styles.disabled];
},
})<{ ownerState: OptionSwatchOwnerState }>(({ theme }) => ({
background: 'transparent',
color: theme.vars.palette.monoA.A600,
'--hovered': theme.vars.palette.monoA.A50,
'--pressed': theme.vars.palette.monoA.A150,

[`&.${optionSwatchClasses.disabled}`]: {
cursor: 'not-allowed',
pointerEvents: 'auto',
},

variants: [
{
props: {
checked: true,
},
style: {
//
},
},
{
props: {
disabled: true,
},
style: {
//
},
},
],
}));

function areEqualValues(a: any, b: any) {
if (typeof b === 'object' && b !== null) {
return a === b;
}

// The value could be a number, the DOM will stringify it anyway.
return String(a) === String(b);
}

export const OptionSwatch = React.forwardRef<HTMLButtonElement, OptionSwatchProps>(function OptionSwatch(inProps, ref) {
const props = useDefaultProps({ props: inProps, name: 'ESOptionSwatch' });
const {
className,
classes: classesProp,
sx,

checked: checkedProp,
defaultChecked,
disabled,

id,
name: nameProp,
value,

inputProps,
inputRef,

onChange: onChangeProp,
} = props;

const radioGroup = useRadioGroup();

let checked = checkedProp;
const onChange = createChainedFunction(onChangeProp as never, radioGroup?.onChange as never);
let name = nameProp;

if (radioGroup) {
if (typeof checked === 'undefined') {
checked = areEqualValues(radioGroup.value, props.value);
}

if (typeof name === 'undefined') {
name = radioGroup.name;
}
}

const ownerState = {
classes: classesProp,
checked,
disabled,
};

const classes = useUtilityClasses(ownerState);

return (
<OptionSwatchRoot
ref={ref}
TouchRippleProps={{ center: true }}
className={clsx(classes.root, className)}
classes={classes}
disabled={disabled}
ownerState={ownerState}
sx={sx}
>
<input
ref={inputRef}
checked={checked}
defaultChecked={defaultChecked}
disabled={disabled}
id={id}
name={name}
type="radio"
value={value}
onChange={onChange}
{...inputProps}
/>
</OptionSwatchRoot>
);
});
63 changes: 63 additions & 0 deletions packages/react/src/components/OptionSwatch/OptionSwatch.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { OptionSwatchClasses } from './OptionSwatch.classes';

import { SxProps, Theme } from '@mui/material';

export interface OptionSwatchProps {
/** Class applied to the root element. */
className?: string;
/** Override or extend the styles applied to the component. */
classes?: Partial<OptionSwatchClasses>;
/** The system prop that allows defining system overrides as well as additional CSS styles. */
sx?: SxProps<Theme>;

/**
* If `true`, the component is checked.
*/
checked?: boolean;

/**
* The default checked state. Use when the component is not controlled.
*/
defaultChecked?: boolean;

/**
* If `true`, the component is disabled.
* @default false
*/
disabled?: boolean;

/**
* The id of the `input` element.
*/
id?: string;

/**
* The name used to reference the value of the control.
* If you don't provide this prop, it falls back to a randomly generated name.
*/
name?: string;

/**
* [Attributes] applied to the `input` element.
*/
inputProps?: React.InputHTMLAttributes<HTMLInputElement>;

/**
* Pass a ref to the `input` element.
*/
inputRef?: React.Ref<HTMLInputElement>;

/**
* The value of the component. The DOM API casts this to a string.
* The browser uses "on" as the default value.
*/
value?: string | number | readonly string[];

/**
* Callback fired when the state is changed.
*
* @param {React.ChangeEvent<HTMLInputElement>} event The event source of the callback.
* You can pull out the new checked state by accessing `event.target.checked` (boolean).
*/
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
}
4 changes: 4 additions & 0 deletions packages/react/src/components/OptionSwatch/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { OptionSwatch } from './OptionSwatch';
export type { OptionSwatchClasses, OptionSwatchClassKey } from './OptionSwatch.classes';
export { optionSwatchClasses } from './OptionSwatch.classes';
export type { OptionSwatchProps } from './OptionSwatch.types';
1 change: 1 addition & 0 deletions packages/react/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export * from './locale';
export * from './MadeBy';
export * from './MenuGroup';
export * from './MenuItem';
export * from './OptionSwatch';
export * from './PageHGroup';
export * from './Pagination';
export * from './PasswordField';
Expand Down
Loading