From ccb84969707d84e57e26ced43c372c6dca20b1bf Mon Sep 17 00:00:00 2001 From: Powerplex Date: Thu, 29 Jun 2023 15:13:52 +0200 Subject: [PATCH] feat(input): inputgroup status indicator --- package-lock.json | 12 +++++++ packages/components/input/src/Input.doc.mdx | 14 ++++---- .../components/input/src/Input.stories.tsx | 18 +++++++---- .../components/input/src/Input.styles.tsx | 10 +++--- packages/components/input/src/Input.tsx | 6 ++-- packages/components/input/src/InputAddon.tsx | 2 +- .../input/src/InputContainer.styles.ts | 2 +- .../components/input/src/InputContainer.tsx | 4 +-- packages/components/input/src/InputGroup.tsx | 14 ++++---- .../components/input/src/InputGroupContext.ts | 4 +-- .../input/src/InputStatusIndicator.tsx | 32 +++++++++++++++++++ packages/components/input/src/index.ts | 15 ++++++--- 12 files changed, 94 insertions(+), 39 deletions(-) create mode 100644 packages/components/input/src/InputStatusIndicator.tsx diff --git a/package-lock.json b/package-lock.json index 205cc527f..595a297ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2498,6 +2498,18 @@ "sisteransi": "^1.0.5" } }, + "node_modules/@clack/prompts/node_modules/is-unicode-supported": { + "version": "1.3.0", + "extraneous": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "dev": true, diff --git a/packages/components/input/src/Input.doc.mdx b/packages/components/input/src/Input.doc.mdx index 6b1d9f6ab..6b21d3959 100644 --- a/packages/components/input/src/Input.doc.mdx +++ b/packages/components/input/src/Input.doc.mdx @@ -21,7 +21,7 @@ npm install @spark-ui/input ## Import ```tsx -import { Input } from '@spark-ui/input' +import { Input, InputGroup } from '@spark-ui/input' ``` ## Props @@ -50,12 +50,6 @@ Use `disabled` prop to indicate the input is disabled. -## Intent - -Use `intent` prop to change the color of the input. - - - ## InputGroup Use `InputGroup` component to group your input with addons and elements like icons. @@ -72,6 +66,12 @@ Use `InputGroup` component to group your input with addons and elements like ico }} /> +## Status + +Use `status` prop to change the color of the input. + + + ### Addons diff --git a/packages/components/input/src/Input.stories.tsx b/packages/components/input/src/Input.stories.tsx index 9a565406d..2168d78f4 100644 --- a/packages/components/input/src/Input.stories.tsx +++ b/packages/components/input/src/Input.stories.tsx @@ -37,13 +37,16 @@ export const Disabled: StoryFn = _args => ( ) -const intents: InputProps['intent'][] = ['neutral', 'success', 'alert', 'error'] +const statuses: InputProps['status'][] = ['success', 'alert', 'error'] -export const Intent: StoryFn = _args => { +export const Status: StoryFn = _args => { return (
- {intents.map(intent => ( - + {statuses.map(status => ( + + + + ))}
) @@ -51,9 +54,10 @@ export const Intent: StoryFn = _args => { export const GroupAddons: StoryFn = _args => { return ( - + https:// + .com ) @@ -61,7 +65,7 @@ export const GroupAddons: StoryFn = _args => { export const GroupElements: StoryFn = _args => { return ( - + @@ -69,7 +73,7 @@ export const GroupElements: StoryFn = _args => { - + diff --git a/packages/components/input/src/Input.styles.tsx b/packages/components/input/src/Input.styles.tsx index 34497aa99..dcab1238c 100644 --- a/packages/components/input/src/Input.styles.tsx +++ b/packages/components/input/src/Input.styles.tsx @@ -2,7 +2,7 @@ import { cva, VariantProps } from 'class-variance-authority' export const inputStyles = cva([], { variants: { - intent: { + status: { neutral: [], success: [], alert: [], @@ -15,7 +15,7 @@ export const inputStyles = cva([], { }, compoundVariants: [ { - intent: 'neutral', + status: 'neutral', isGrouped: false, class: [ 'border-outline', @@ -25,17 +25,17 @@ export const inputStyles = cva([], { ], }, { - intent: 'success', + status: 'success', isGrouped: false, class: ['border-success', 'ring-success'], }, { - intent: 'alert', + status: 'alert', isGrouped: false, class: ['border-alert', 'ring-alert'], }, { - intent: 'error', + status: 'error', isGrouped: false, class: ['border-error', 'ring-error'], }, diff --git a/packages/components/input/src/Input.tsx b/packages/components/input/src/Input.tsx index 7f3d7c8d8..f071934bc 100644 --- a/packages/components/input/src/Input.tsx +++ b/packages/components/input/src/Input.tsx @@ -8,16 +8,16 @@ import { InputPrimitive, InputPrimitiveProps } from './InputPrimitive' export interface InputProps extends InputPrimitiveProps, InputStylesProps {} export const Input = forwardRef( - ({ className: classNameProp, intent: intentProp = 'neutral', ...others }, ref) => { + ({ className: classNameProp, status: statusProp = 'neutral', ...others }, ref) => { const field = useFormFieldControl() const group = useInputGroup() const isGrouped = !!group - const intent = field.state ?? intentProp + const status = field.state ?? statusProp return ( ) diff --git a/packages/components/input/src/InputAddon.tsx b/packages/components/input/src/InputAddon.tsx index 092191d40..5f6f71f6f 100644 --- a/packages/components/input/src/InputAddon.tsx +++ b/packages/components/input/src/InputAddon.tsx @@ -5,7 +5,7 @@ import { useInputGroup } from './InputGroupContext' export interface InputAddonProps extends ComponentPropsWithoutRef<'div'>, - Omit {} + Omit {} export const InputAddon = forwardRef>( ({ className, ...others }, ref) => { diff --git a/packages/components/input/src/InputContainer.styles.ts b/packages/components/input/src/InputContainer.styles.ts index bb1ae380a..89f01e6ef 100644 --- a/packages/components/input/src/InputContainer.styles.ts +++ b/packages/components/input/src/InputContainer.styles.ts @@ -16,7 +16,7 @@ export const inputContainerStyles = cva( ], { variants: { - intent: { + status: { neutral: [ 'border-outline', 'peer-hover:border-outline-high', diff --git a/packages/components/input/src/InputContainer.tsx b/packages/components/input/src/InputContainer.tsx index 216f79a06..6a92faf8a 100644 --- a/packages/components/input/src/InputContainer.tsx +++ b/packages/components/input/src/InputContainer.tsx @@ -10,10 +10,10 @@ export interface InputContainerProps } export const InputContainer = forwardRef>( - ({ className, intent, asChild, ...others }, ref) => { + ({ className, status = 'neutral', asChild, ...others }, ref) => { const Component = asChild ? Slot : 'div' - return + return } ) diff --git a/packages/components/input/src/InputGroup.tsx b/packages/components/input/src/InputGroup.tsx index fa7c0f1af..aad2d2c38 100644 --- a/packages/components/input/src/InputGroup.tsx +++ b/packages/components/input/src/InputGroup.tsx @@ -16,18 +16,18 @@ import { InputContainer, InputContainerProps } from './InputContainer' import { inputGroupStyles, InputGroupStylesProps } from './InputGroup.styles' import { InputGroupContext } from './InputGroupContext' export interface InputGroupProps extends ComponentPropsWithoutRef<'div'>, InputGroupStylesProps { - intent?: InputContainerProps['intent'] + status?: InputContainerProps['status'] isDisabled?: boolean } export const InputGroup = forwardRef>( ( - { className, children: childrenProp, intent: intentProp = 'neutral', isDisabled, ...others }, + { className, children: childrenProp, status: statusProp = 'neutral', isDisabled, ...others }, ref ) => { const { state } = useFormFieldControl() const children = Children.toArray(childrenProp).filter(isValidElement) - const intent = state ?? intentProp + const status = state ?? statusProp const getDisplayName = (element?: ReactElement) => { return element ? (element.type as FC).displayName : '' @@ -38,6 +38,7 @@ export const InputGroup = forwardRef { return { + status, isDisabled: !!isDisabled, isLeftElementVisible, isRightElementVisible, @@ -55,6 +57,7 @@ export const InputGroup = forwardRef {input} - + {isLeftElementVisible && left} - {isRightElementVisible && right} ) : ( @@ -88,13 +90,13 @@ export const InputGroup = forwardRef {isLeftElementVisible && left} - {isRightElementVisible && right} ), }) )} + {statusIndicator} {isRightAddonVisible && right} diff --git a/packages/components/input/src/InputGroupContext.ts b/packages/components/input/src/InputGroupContext.ts index d478e76e4..7d3ce2142 100644 --- a/packages/components/input/src/InputGroupContext.ts +++ b/packages/components/input/src/InputGroupContext.ts @@ -1,8 +1,8 @@ import { createContext, useContext } from 'react' -import { InputContainerProps } from './InputContainer' +import { type InputContainerProps } from './InputContainer' -export interface InputGroupContext extends Pick { +export interface InputGroupContext extends Pick { isDisabled?: boolean isLeftElementVisible: boolean isRightElementVisible: boolean diff --git a/packages/components/input/src/InputStatusIndicator.tsx b/packages/components/input/src/InputStatusIndicator.tsx new file mode 100644 index 000000000..c5e806456 --- /dev/null +++ b/packages/components/input/src/InputStatusIndicator.tsx @@ -0,0 +1,32 @@ +import { Icon } from '@spark-ui/icon' +import { AlertOutline } from '@spark-ui/icons/dist/icons/AlertOutline' +import { Check } from '@spark-ui/icons/dist/icons/Check' +import { WarningOutline } from '@spark-ui/icons/dist/icons/WarningOutline' + +import { useInputGroup } from './InputGroupContext' + +export type InputStatusIndicatorProps = any + +export const InputStatusIndicator = () => { + const group = useInputGroup() + + if (!group?.status || group?.status === 'neutral') return null + + const { status } = group + + const statusMap = { + error: { icon: }, + alert: { icon: }, + success: { icon: }, + } + + return ( +
+ + {statusMap[status].icon} + +
+ ) +} + +InputStatusIndicator.displayName = 'InputStatusIndicator' diff --git a/packages/components/input/src/index.ts b/packages/components/input/src/index.ts index 5bf46ba90..c7349efca 100644 --- a/packages/components/input/src/index.ts +++ b/packages/components/input/src/index.ts @@ -1,10 +1,11 @@ import { FC } from 'react' -import { InputGroup as Root, InputGroupProps } from './InputGroup' -import { InputLeftAddon, InputLeftAddonProps } from './InputLeftAddon' -import { InputLeftElement, InputLeftElementProps } from './InputLeftElement' -import { InputRightAddon, InputRightAddonProps } from './InputRightAddon' -import { InputRightElement, InputRightElementProps } from './InputRightElement' +import { InputGroup as Root, type InputGroupProps } from './InputGroup' +import { InputLeftAddon, type InputLeftAddonProps } from './InputLeftAddon' +import { InputLeftElement, type InputLeftElementProps } from './InputLeftElement' +import { InputRightAddon, type InputRightAddonProps } from './InputRightAddon' +import { InputRightElement, type InputRightElementProps } from './InputRightElement' +import { InputStatusIndicator, type InputStatusIndicatorProps } from './InputStatusIndicator' export { useInputGroup } from './InputGroupContext' @@ -16,20 +17,24 @@ export { type InputLeftAddonProps } from './InputLeftAddon' export { type InputLeftElementProps } from './InputLeftElement' export { type InputRightAddonProps } from './InputRightAddon' export { type InputRightElementProps } from './InputRightElement' +export { type InputStatusIndicatorProps } from './InputStatusIndicator' export const InputGroup: FC & { LeftAddon: FC RightAddon: FC LeftElement: FC RightElement: FC + StatusIndicator: FC } = Object.assign(Root, { LeftAddon: InputLeftAddon, RightAddon: InputRightAddon, LeftElement: InputLeftElement, RightElement: InputRightElement, + StatusIndicator: InputStatusIndicator, }) InputGroup.LeftAddon.displayName = 'InputGroup.LeftAddon' InputGroup.RightAddon.displayName = 'InputGroup.RightAddon' InputGroup.LeftElement.displayName = 'InputGroup.LeftElement' InputGroup.RightElement.displayName = 'InputGroup.RightElement' +InputGroup.StatusIndicator.displayName = 'InputGroup.StatusIndicator'