8000 feat(Price): add new component by feerzlay · Pull Request #411 · Elonsoft/esfront · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat(Price): add new component #411

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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/Price/Price.api.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Meta } from '@storybook/blocks';
import LinkTo from '@storybook/addon-links/react';
import { TableInterface } from '~storybook/components/TableInterface';

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

# Price API

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

## Component name

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

## Props

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

<br />

## CSS

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

<br />

## Demos

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

export type PriceClasses = {
/** Styles applied to the root element. */
root: string;
/** Styles applied to the root element if old=true. */
old: string;
/** Styles applied to the price element. */
price: string;
/** Styles applied to the currency element. */
currency: string;
/** Styles applied to the line element. */
line: string;
};
export type PriceClassKey = keyof PriceClasses;

export function getPriceUtilityClass(slot: string): string {
return generateUtilityClass('ESPrice', slot);
}

export const priceClasses: PriceClasses = generateUtilityClasses('ESPrice', [
'root',
'old',
'price',
'currency',
'line',
]);
42 changes: 42 additions & 0 deletions packages/react/src/components/Price/Price.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Meta, StoryObj } from '@storybook/react';

import { Price } from '.';

const meta: Meta<typeof Price> = {
tags: ['autodocs'],
component: Price,
parameters: {
references: ['Price'],
},
argTypes: {
children: {
control: {
type: 'number',
},
},
currency: {
control: {
type: 'text',
},
},
locales: {
table: {
disable: true,
},
},
},
args: {
children: 9000,
currency: 'RUB',
},
};

export default meta;

type Story = StoryObj<typeof Price>;

export const Demo: Story = {
render: ({ children, ...args }) => {
return <Price {...args}>{+children || 0}</Price>;
},
};
145 changes: 145 additions & 0 deletions packages/react/src/components/Price/Price.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { PriceProps } from './Price.types';

import clsx from 'clsx';
import { getPriceUtilityClass, priceClasses } from './Price.classes';

import { unstable_composeClasses as composeClasses } from '@mui/base';

import { styled, useThemeProps } from '@mui/material/styles';

type PriceOwnerState = {
classes?: PriceProps['classes'];
old?: PriceProps['old'];
};

const useUtilityClasses = (ownerState: PriceOwnerState) => {
const { classes, old } = ownerState;

const slots = {
root: ['root', old && 'old'],
old: ['old'],
price: ['price'],
currency: ['currency'],
line: ['line'],
};

return composeClasses(slots, getPriceUtilityClass, classes);
};

const PriceRoot = styled('div', {
name: 'ESPrice',
slot: 'Root',
overridesResolver: (props, styles) => {
const {
ownerState: { old },
} = props;

return [styles.root, old && styles.old];
},
})<{ ownerState: PriceOwnerState }>(({ theme }) => ({
...theme.typography.h5,

position: 'relative',
display: 'flex',
alignItems: 'center',
gap: '4px',
width: 'max-content',

variants: [
{
props: {
old: true,
},
style: {
...theme.typography.body100,

[`& .${priceClasses.price}, & .${priceClasses.currency}`]: {
color: theme.vars.palette.monoA.A600,
},
},
},
],
}));

const PricePrice = styled('span', {
name: 'ESPrice',
slot: 'Price',
overridesResolver: (props, styles) => styles.price,
})(({ theme }) => ({
color: theme.vars.palette.monoA.A900,
}));

const PriceCurrency = styled('span', {
name: 'ESPrice',
slot: 'Currency',
overridesResolver: (props, styles) => styles.currency,
})(({ theme }) => ({
color: theme.vars.palette.monoA.A500,
}));

const PriceLine = styled('div', {
name: 'ESPrice',
slot: 'Line',
overridesResolver: (props, styles) => styles.line,
})(({ theme }) => ({
position: 'absolute',
top: '50%',
left: '-1px',
right: '-1px',
transform: 'translateY(-50%)',
height: '2px',
backgroundColor: theme.vars.palette.error.A800,
borderRadius: '4px',
}));

const getCurrencySymbol = (locales: Intl.LocalesArgument, currency: string) => {
return Number(0)
.toLocaleString(locales, {
style: 'currency',
currency,
minimumFractionDigits: 0,
maximumFractionDigits: 0,
})
.replace(/\d/g, '')
.trim();
};

/**
* Display price of the product.
*/
export const Price = (inProps: PriceProps) => {
const {
className,
classes: inClasses,
children,
sx,
currency,
old,
locales,
minimumFractionDigits,
maximumFractionDigits,
disableMicrodata,
} = useThemeProps({
props: inProps,
name: 'ESPrice',
});

const ownerState = { classes: inClasses, old };
const classes = useUtilityClasses(ownerState);

return (
<PriceRoot className={clsx(className, classes.root)} ownerState={ownerState} sx={sx}>
{!old && !disableMicrodata && (
<>
<meta content={currency} itemProp="priceCurrency" />
<meta content={children.toFixed(2)} itemProp="price" />
</>
)}
<PricePrice className={classes.price}>
{Number(children).toLocaleString(locales, { minimumFractionDigits, maximumFractionDigits })}
</PricePrice>
<PriceCurrency className={classes.currency}>{getCurrencySymbol(locales, currency)}</PriceCurrency>
{!!old && <PriceLine className={classes.line} />}
</PriceRoot>
);
};
29 changes: 29 additions & 0 deletions packages/react/src/components/Price/Price.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { PriceClasses } from './Price.classes';

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

export interface PriceProps {
children: number;
/** The currency to use in formatting. Possible values are the ISO 4217 currency codes. */
currency: string;

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

/** If true, the price is old. */
old?: boolean;

/** A string with a BCP 47 language tag, or an array of such strings. */
locales?: Intl.LocalesArgument;
/** The minimum number of fraction digits to use. */
minimumFractionDigits?: number;
/** The maximum number of fraction digits to use. */
maximumFractionDigits?: number;

/** If true, the microdata meta tags will not be rendered. */
disableMicrodata?: boolean;
}
4 changes: 4 additions & 0 deletions packages/react/src/components/Price/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { Price } from './Price';
export type { PriceClasses, PriceClassKey } from './Price.classes';
export { priceClasses } from './Price.classes';
export type { PriceProps } from './Price.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 @@ -45,6 +45,7 @@ export * from './MenuItem';
export * from './PageHGroup';
export * from './Pagination';
export * from './PasswordField';
export * from './Price';
export * from './Radio';
export * from './RadioGroup';
export * from './RibbonBadge';
Expand Down
14 changes: 14 additions & 0 deletions packages/react/src/components/locale/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { GalleryActionsProps, GalleryDescriptionProps, GalleryMetaProps, Gallery
import { MadeByProps } from '../MadeBy';
import { PaginationPagesProps, PaginationRangeProps } from '../Pagination';
import { PasswordFieldProps } from '../PasswordField';
import { PriceProps } from '../Price';
import { SFSChipsProps, SFSFiltersProps, SFSSearchProps, SFSSortingProps } from '../SFS';
import { SidebarItemProps, SidebarToggleProps } from '../Sidebar';
import { SnackbarCloseProps } from '../Snackbar';
Expand Down Expand Up @@ -84,6 +85,9 @@ export interface Localization {
ESPasswordField: {
defaultProps: Pick<PasswordFieldProps, 'labelHidePassword' | 'labelShowPassword'>;
};
ESPrice: {
defaultProps: Pick<PriceProps, 'locales'>;
};
ESSidebarToggle: {
defaultProps: Pick<SidebarToggleProps, 'labelOpen' | 'labelHide'>;
};
Expand Down Expand Up @@ -249,6 +253,11 @@ export const en: Localization = {
labelShowPassword: 'Show',
},
},
ESPrice: {
defaultProps: {
locales: 'en-US',
},
},
ESSidebarToggle: {
defaultProps: {
labelOpen: 'Expand',
Expand Down Expand Up @@ -440,6 +449,11 @@ export const ru: Localization = {
labelShowPassword: 'Показать',
},
},
ESPrice: {
defaultProps: {
locales: 'ru-RU',
},
},
ESSidebarToggle: {
defaultProps: {
labelOpen: 'Развернуть',
Expand Down
7 changes: 7 additions & 0 deletions packages/react/src/overrides.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ import {
PaginationRangeProps,
} from './components/Pagination';
import { PasswordFieldClassKey, PasswordFieldProps } from './components/PasswordField';
import { PriceClassKey, PriceProps } from './components/Price';
import { RadioClassKey, RadioIconClassKey, RadioIconProps, RadioProps } from './components/Radio';
import { RadioGroupClassKey, RadioGroupProps } from './components/RadioGroup';
import { RibbonBadgeClassKey, RibbonBadgeProps } from './components/RibbonBadge';
Expand Down Expand Up @@ -440,6 +441,7 @@ declare module '@mui/material/styles/props' {
ESPaginationPages: PaginationPagesProps;
ESPaginationRange: PaginationRangeProps;
ESPasswordField: PasswordFieldProps;
ESPrice: PriceProps;
ESRadio: RadioProps;
ESRadioIcon: RadioIconProps;
ESRadioGroup: RadioGroupProps;
Expand Down Expand Up @@ -588,6 +590,7 @@ declare module '@mui/material/styles/overrides' {
ESPaginationPages: PaginationPagesClassKey;
ESPaginationRange: PaginationRangeClassKey;
ESPasswordField: PasswordFieldClassKey;
ESPrice: PriceClassKey;
ESRadio: RadioClassKey;
ESRadioIcon: RadioIconClassKey;
ESRadioGroup: RadioGroupClassKey;
Expand Down Expand Up @@ -1024,6 +1027,10 @@ declare module '@mui/material/styles/components' {
defaultProps?: ComponentsProps['ESPasswordField'];
styleOverrides?: ComponentsOverrides['ESPasswordField'];
};
ESPrice?: {
defaultProps?: ComponentsProps['ESPrice'];
styleOverrides?: ComponentsOverrides['ESPrice'];
};
ESRadio?: {
defaultProps?: ComponentsProps['ESRadio'];
styleOverrides?: ComponentsOverrides['ESRadio'];
Expand Down
Loading
0