Skip to content

Commit

Permalink
feat: base ui components
Browse files Browse the repository at this point in the history
Signed-off-by: neil <msg@nancode.cn>
  • Loading branch information
nanzm committed Jul 26, 2023
1 parent 4285ee6 commit acc9117
Show file tree
Hide file tree
Showing 11 changed files with 428 additions and 2 deletions.
51 changes: 51 additions & 0 deletions packages/storybook/src/stories/Input.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { Meta, StoryObj } from '@storybook/react';

import { Input } from '@oss-compass/ui';

const meta: Meta<typeof Input> = {
title: 'basic/Input',
component: Input,
argTypes: {
disabled: {
control: 'boolean',
},
error: {
control: 'boolean',
},
placeholder: {
control: 'string',
},
},
};

export default meta;

type Story = StoryObj<typeof Input>;

export const Default: Story = {
args: { disabled: false, error: false, placeholder: 'please input' },
render: (args) => {
return <Input intent={'primary'} style={{ width: '400px' }} {...args} />;
},
};

export const Secondary: Story = {
args: { disabled: false, error: false, placeholder: 'please input' },
render: (args) => {
return <Input intent={'secondary'} style={{ width: '400px' }} {...args} />;
},
};

export const Md: Story = {
args: { disabled: false, error: false, placeholder: 'please input' },
render: (args) => {
return <Input size="md" style={{ width: '400px' }} {...args} />;
},
};

export const Lg: Story = {
args: { disabled: false, error: false, placeholder: 'please input' },
render: (args) => {
return <Input size="lg" {...args} />;
},
};
67 changes: 67 additions & 0 deletions packages/storybook/src/stories/Redio.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { Meta, StoryObj } from '@storybook/react';

import RadioGroup from '@mui/material/RadioGroup';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormControl from '@mui/material/FormControl';
import FormLabel from '@mui/material/FormLabel';

import { CustomRadio } from '@oss-compass/ui';

const meta: Meta<typeof RadioGroup> = {
title: 'basic/RadioGroup',
component: RadioGroup,
argTypes: {
defaultValue: {
options: ['female', 'male', 'other'],
control: { type: 'radio' },
},
value: {
options: ['female', 'male', 'other'],
control: { type: 'radio' },
},
},
};

export default meta;

type Story = StoryObj<typeof RadioGroup>;

export const CustomStyledRadio: Story = {
args: { defaultValue: 'first' },
render: (args) => {
const { defaultValue, value } = args;
return (
<FormControl>
<FormLabel id="demo-customized-radios">Gender</FormLabel>
<RadioGroup
defaultValue={defaultValue}
value={value}
aria-labelledby="demo-customized-radios"
name="customized-radios"
>
<FormControlLabel
value="female"
control={<CustomRadio />}
label="Female"
/>
<FormControlLabel
value="male"
control={<CustomRadio />}
label="Male"
/>
<FormControlLabel
value="other"
control={<CustomRadio />}
label="Other"
/>
<FormControlLabel
value="disabled"
disabled
control={<CustomRadio />}
label="(Disabled option)"
/>
</RadioGroup>
</FormControl>
);
},
};
26 changes: 26 additions & 0 deletions packages/storybook/src/stories/Select.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { Meta, StoryObj } from '@storybook/react';

import { Select, SelectOption } from '@oss-compass/ui';

const meta: Meta<typeof Select> = {
title: 'basic/Select',
component: Select,
argTypes: {},
};

export default meta;
type Story = StoryObj<typeof Select>;

export const Default: Story = {
args: {},
render: (args) => {
return (
<Select>
<SelectOption value={'1'}>Frodo</SelectOption>
<SelectOption value={'2'}>Sam</SelectOption>
<SelectOption value={'3'}>Merry</SelectOption>
<SelectOption value={'4'}>Pippin</SelectOption>
</Select>
);
},
};
10 changes: 9 additions & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,21 @@
"author": "",
"scripts": {},
"dependencies": {
"react": "18.2.0",
"@mui/base": "^5.0.0-beta.8",
"@mui/material": "^5.10.13",
"@mui/icons-material": "^5.14.1",
"@radix-ui/react-radio-group": "^1.1.3",
"ahooks": "^3.7.8",
"class-variance-authority": "^0.6.0",
"classnames": "^2.3.1",
"tailwind-merge": "^1.12.0",
"react-icons": "^4.10.1"
},
"devDependencies": {
"typescript": "4.7.4"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0"
}
}
82 changes: 82 additions & 0 deletions packages/ui/src/components/Input/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React, { forwardRef, FocusEvent, ChangeEvent } from 'react';
import classnames from 'classnames';
import { cva, type VariantProps } from 'class-variance-authority';
import { twMerge } from 'tailwind-merge';
import { useControllableValue } from 'ahooks';

interface InputProps {
defaultValue?: string;
name?: string;
value?: string;
style?: React.CSSProperties;
onChange?: (value: string) => void;
onBlur?: (e: FocusEvent<HTMLInputElement>) => void;
className?: string;
placeholder?: string;
error?: boolean;
disabled?: boolean;
}

const inputVariants = cva(' w-full text-base outline-none', {
variants: {
intent: {
primary: 'border-solid border-black',
secondary: 'border-solid border-[#CCCCCC]',
},
size: {
lg: 'h-12 px-3 border-2',
md: 'h-10 px-3 border',
},
},
defaultVariants: {
intent: 'primary',
size: 'md',
},
});

interface InputVariants extends VariantProps<typeof inputVariants> {}

export const Input = forwardRef<HTMLInputElement, InputProps & InputVariants>(
(props, ref) => {
const {
intent,
size,
name,
className,
style,
onBlur,
placeholder,
disabled = false,
error = false,
} = props;

const [state, setState] = useControllableValue<string>(props, {
defaultValuePropName: 'defaultValue',
valuePropName: 'value',
trigger: 'onChange',
});

const cls = classnames(
inputVariants({ intent, size }),
[error ? 'border-red-500' : ''],
className
);

return (
<input
ref={ref}
name={name}
type="text"
value={state}
style={style}
onChange={(e) => setState(e.target.value)}
onBlur={(e) => onBlur?.(e)}
placeholder={placeholder}
disabled={disabled}
className={twMerge(cls)}
/>
);
}
);

Input.displayName = 'Input';
17 changes: 17 additions & 0 deletions packages/ui/src/components/Redio/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { styled } from '@mui/material/styles';
import Radio, { RadioProps } from '@mui/material/Radio';

export function CustomRadio(props: RadioProps) {
return (
<Radio
disableRipple
sx={{
color: '#868690',
'&.Mui-checked': {
color: '#3A5BEF',
},
}}
{...props}
/>
);
}
45 changes: 45 additions & 0 deletions packages/ui/src/components/Select/Options.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as React from 'react';
import { twMerge } from 'tailwind-merge';
import BaseOption, { OptionProps, OptionOwnerState } from '@mui/base/Option';
// import useIsDarkMode from '../shared/useIsDarkMode';

const getOptionColorClasses = ({
selected,
highlighted,
disabled,
}: Partial<OptionOwnerState<number>>) => {
let classes = '';
if (disabled) {
classes += 'text-slate-400 ';
} else {
if (selected) {
classes += '!bg-primary !text-white ';
} else if (highlighted) {
classes += 'bg-slate-100 text-slate-900 ';
}
classes += 'hover:bg-slate-100 hover:text-slate-900';
}
return classes;
};

export const SelectOption = React.forwardRef<HTMLLIElement, OptionProps<any>>(
(props, ref) => {
return (
<BaseOption
ref={ref}
{...props}
slotProps={{
root: ({ selected, highlighted, disabled }) => ({
className: twMerge(
`list-none p-2 mb-1 cursor-pointer last-of-type:border-b-0 ${getOptionColorClasses(
{ selected, highlighted, disabled }
)}`
),
}),
}}
/>
);
}
);

SelectOption.displayName = 'SelectOption';
43 changes: 43 additions & 0 deletions packages/ui/src/components/Select/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React, { PropsWithChildren, forwardRef } from 'react';
import SelectBase, { SelectProps, SelectSlots } from '@mui/base/Select';
import useIsDarkMode from '../shared/useIsDarkMode';

export const Select = forwardRef(function Select<
OptionValue,
Multiple extends boolean
>(
props: SelectProps<OptionValue, Multiple>,
ref: React.ForwardedRef<HTMLButtonElement>
) {
const { children } = props;
const isDarkMode = useIsDarkMode();

return (
<div className={isDarkMode ? 'dark' : ''}>
<SelectBase
ref={ref}
slotProps={{
root: ({ focusVisible, open }) => ({
className: `text-sm box-border w-80 px-3 py-2 text-left bg-white border border-solid border-[#ccc] text-slate-900 transition-all hover:bg-slate-50 outline-0 ${
focusVisible ? 'border-[#ccc] shadow-outline-purple' : ''
} ${
open ? 'after:content-["▴"]' : 'after:content-["▾"]'
} after:float-right`,
}),
listbox: {
className: `text-sm p-1.5 my-1.5 w-80 overflow-auto outline-0 bg-white border border-solid border-slate-200 text-slate-900 shadow shadow-slate-200 `,
},
popper: { className: `${isDarkMode ? 'dark' : ''} z-10` },
}}
{...props}
>
{children}
</SelectBase>
</div>
);
}) as <OptionValue, Multiple extends boolean>(
props: SelectProps<OptionValue, Multiple> &
React.RefAttributes<HTMLUListElement>
) => JSX.Element;

export * from './Options';
7 changes: 7 additions & 0 deletions packages/ui/src/components/shared/useIsDarkMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { useTheme } from '@mui/system';

export default function useIsDarkMode() {
// todo
const theme = useTheme();
return theme.palette.mode === 'dark';
}
3 changes: 3 additions & 0 deletions packages/ui/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export * from './components/Button';
export * from './components/Input';
export * from './components/Redio';
export * from './components/Select';
Loading

2 comments on commit acc9117

@vercel
Copy link

@vercel vercel bot commented on acc9117 Jul 26, 2023

Choose a reason for hiding this comment

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

@vercel
Copy link

@vercel vercel bot commented on acc9117 Jul 26, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.