Skip to content

Commit

Permalink
Form fields min-width issue + direction enhancement (#2341)
Browse files Browse the repository at this point in the history
* Minimal width Form field elements fixed and Form field label orientation enhanced with the direction to flow horizontally or vertically

* indent fix

* format and linting fixes
  • Loading branch information
isaozler authored Jun 28, 2024
1 parent 8065956 commit df0a2e8
Show file tree
Hide file tree
Showing 21 changed files with 257 additions and 50 deletions.
5 changes: 5 additions & 0 deletions .changeset/hot-apricots-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kadena/react-ui": patch
---

Minimal width Form field elements fixed and Form field label orientation enhanced with the direction to flow horizontally or vertically
18 changes: 9 additions & 9 deletions packages/libs/react-ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
basic React components for reuse in Kadena applications. It uses
[vanilla-extract/css][1] (will be referred to as VE) to establish a system of
utility classes (defined as [sprinkles][2]) and CSS variables (defined in the
theme) that align with Kadena's [Kode Design System][3] and exposes them so that they
can be used with any project or framework. A basic [Storybook][4] integration
has been implemented so that users can preview components visually and interact
with their configuration options.
theme) that align with Kadena's [Kode Design System][3] and exposes them so that
they can be used with any project or framework. A basic [Storybook][4]
integration has been implemented so that users can preview components visually
and interact with their configuration options.

> Warning: This library is in its early development stage so elements in the
> styling environment may change as well as the API for components.
Expand Down Expand Up @@ -184,11 +184,11 @@ CSS-in-JS library that is framework agnostic.

_Theming_

We have defined a theme using elements of the [Kode Design System][3] and
these tokens should be used as property values in most cases to ensure
consistency and alignment with the design. With VE, we are also able to override
this theme within projects to add additional CSS variables or update colors for
a dark theme, for example.
We have defined a theme using elements of the [Kode Design System][3] and these
tokens should be used as property values in most cases to ensure consistency and
alignment with the design. With VE, we are also able to override this theme
within projects to add additional CSS variables or update colors for a dark
theme, for example.

_Atoms_

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,17 @@ const meta: Meta<ICheckboxProps> = {
type: 'boolean',
},
},
formFieldDirection: {
description: 'Orientation of the form header labels',
control: {
type: 'radio',
},
options: {
Row: 'row',
Column: 'column',
},
defaultValue: 'row',
},
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { AriaCheckboxGroupProps } from 'react-aria';
import { useCheckboxGroup } from 'react-aria';
import type { CheckboxGroupState } from 'react-stately';
import { useCheckboxGroupState } from 'react-stately';
import type { FormFieldDirection } from '../FormFieldHeader/FormFieldHeader';
import { FormFieldHeader } from '../FormFieldHeader/FormFieldHeader';
import { FormFieldHelpText } from '../FormFieldHelpText/FormFieldHelpText';
import { groupClass, layoutClass } from './Checkbox.css';
Expand All @@ -19,6 +20,7 @@ export interface ICheckboxProps extends AriaCheckboxGroupProps {
isReadOnly?: boolean;
label?: string;
tag?: string;
formFieldDirection?: FormFieldDirection;
}

export const CheckboxContext = createContext<CheckboxGroupState | null>(null);
Expand All @@ -36,6 +38,7 @@ export function CheckboxGroup(props: ICheckboxProps) {
isReadOnly,
label,
tag,
formFieldDirection = 'row',
} = props;
const state = useCheckboxGroupState(props);
const {
Expand All @@ -60,6 +63,7 @@ export function CheckboxGroup(props: ICheckboxProps) {
tag={tag}
info={info}
isDisabled={isDisabled}
direction={formFieldDirection}
{...labelProps}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ListBox } from '../../ListBox';
import { Popover } from '../../Popover';
import { Field } from '../Field/Field';
import { input } from '../Form.css';
import type { FormFieldDirection } from '../FormFieldHeader/FormFieldHeader';
import { comboBoxControlClass } from './Combobox.css';

type Variants = NonNullable<RecipeVariants<typeof input>>;
Expand All @@ -28,6 +29,7 @@ export interface IComboboxProps<T extends object = any>
className?: string;
tag?: string;
info?: string;
direction?: FormFieldDirection;
/*
* @deprecated Use `isDisabled` instead. only here to support libs that manages props like `react-hook-form`
*/
Expand Down Expand Up @@ -58,6 +60,7 @@ function ComboBoxBase<T extends object>(
variant = 'default',
startVisual,
label,
direction = 'row',
} = props;

const { contains } = useFilter({ sensitivity: 'base' });
Expand Down Expand Up @@ -98,6 +101,7 @@ function ComboBoxBase<T extends object>(
label={label}
isDisabled={isDisabled}
description={description}
direction={direction}
errorMessage={errorMessage}
size={size}
tag={tag}
Expand Down
4 changes: 4 additions & 0 deletions packages/libs/react-ui/src/components/Form/Field/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
startAddonSize,
startAddonStyles,
} from '../Form.css';
import type { FormFieldDirection } from '../FormFieldHeader/FormFieldHeader';
import { FormFieldHelpText } from '../FormFieldHelpText/FormFieldHelpText';

interface IFieldProps {
Expand All @@ -33,6 +34,7 @@ interface IFieldProps {
validationDetails: ValidityState;
validationErrors: string[];
inlineVisuals?: boolean;
direction?: FormFieldDirection;
}

const Field = forwardRef(
Expand All @@ -57,6 +59,7 @@ const Field = forwardRef(
validationErrors,
variant = 'default',
inlineVisuals = false,
direction = 'row',
}: IFieldProps,
ref: ForwardedRef<ElementRef<'input' | 'textarea' | 'select' | 'button'>>,
) => {
Expand Down Expand Up @@ -84,6 +87,7 @@ const Field = forwardRef(
tag={tag}
info={info}
isDisabled={isDisabled}
direction={direction}
{...labelProps}
/>
)}
Expand Down
2 changes: 1 addition & 1 deletion packages/libs/react-ui/src/components/Form/Form.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export const baseContainerClass = recipe({
width: '100%',
transition: 'outline-color 0.2s ease-in-out',
outlineColor: 'transparent',
minWidth: '150px',
...responsiveStyle({
sm: {
maxWidth: '100%',
Expand Down Expand Up @@ -254,6 +253,7 @@ export const input = recipe({
{
outline: 'none',
flex: 1,
width: '100%',
paddingInlineStart: token('size.n4'),
border: 'none',
backgroundColor: token('color.background.input.default'),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { style, token } from '../../../styles';
import { style, styleVariants, token } from '../../../styles';

export const headerClass = style({
display: 'flex',
Expand Down Expand Up @@ -39,3 +39,23 @@ export const disabledLabelClass = style({
fontWeight: token('typography.weight.secondaryFont.bold'),
color: token('color.text.base.@disabled'),
});

export const directionClass = styleVariants({
column: {
flexDirection: 'column',
alignItems: 'flex-start',
},
row: {
flexDirection: 'row',
alignItems: 'center',
},
});

export const directionInfoClass = styleVariants({
column: {
marginInlineStart: 'unset',
},
row: {
marginInlineStart: 'auto',
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@ import classNames from 'classnames';
import type { ComponentProps, FC, ReactNode } from 'react';
import React from 'react';
import {
directionClass,
directionInfoClass,
disabledLabelClass,
headerClass,
infoClass,
labelClass,
tagClass,
} from './FormFieldHeader.css';
export type FormFieldDirection = NonNullable<keyof typeof directionInfoClass>;

export interface IFormFieldHeaderProps extends ComponentProps<'label'> {
label: ReactNode;
tag?: string;
info?: string;
className?: string;
isDisabled?: boolean;
direction?: FormFieldDirection;
}

export const FormFieldHeader: FC<IFormFieldHeaderProps> = ({
Expand All @@ -24,16 +28,28 @@ export const FormFieldHeader: FC<IFormFieldHeaderProps> = ({
info,
className,
isDisabled,
direction,
...rest
}) => {
return (
<div className={classNames(headerClass, className)}>
<div
className={classNames(
headerClass,
className,
direction && directionClass[direction],
)}
>
<label {...rest} className={isDisabled ? disabledLabelClass : labelClass}>
{label}
</label>
{Boolean(tag) && <span className={tagClass}>{tag}</span>}
{Boolean(info) && (
<span className={infoClass}>
<span
className={classNames(
infoClass,
direction && directionInfoClass[direction],
)}
>
{info}
<MonoErrorOutline />
</span>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { styleVariants, token } from '../../../styles';
import { style, styleVariants, token } from '../../../styles';

export const iconSize = styleVariants({
sm: {
Expand All @@ -11,3 +11,28 @@ export const iconSize = styleVariants({
fontSize: token('size.n4'),
},
});

/** The add-on icons should be bigger because of the
* functionality than the start visuals and have
* separate style variants.
*/
export const addOnIconSize = styleVariants({
sm: {
fontSize: token('size.n3'),
},
md: {
fontSize: token('size.n4'),
},
lg: {
fontSize: token('size.n6'),
},
});

export const addOnStyleClass = style({
aspectRatio: '1/1',
});

/** Storybook example style */
export const smallInputWrapper = style({
maxWidth: token('screen.resolutions.width.mobile.apple.iphone_se'),
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { MonoAccountCircle } from '@kadena/react-icons/system';
import { Form } from '../Form';
import { input } from '../Form.css';
import type { INumberFieldProps } from './NumberField';
import { smallInputWrapper } from './NumberField.css';

const { variant, fontType, size } = getVariants(input);

Expand All @@ -23,6 +24,14 @@ const formStoryClass = atoms({
gap: 'md',
});

const formStorySmallInputsClass = atoms({
display: 'flex',
flexDirection: 'row',
gap: 'md',
maxWidth: 'content.maxWidth',
backgroundColor: 'accent.primary.default',
});

const meta: Meta<INumberFieldProps> = {
title: 'Form/NumberField',
component: NumberField,
Expand Down Expand Up @@ -151,6 +160,17 @@ const meta: Meta<INumberFieldProps> = {
disable: true,
},
},
direction: {
description: 'Orientation of the form header labels',
control: {
type: 'radio',
},
options: {
Row: 'row',
Column: 'column',
},
defaultValue: 'row',
},
},
args: {
fontType: 'ui',
Expand All @@ -164,6 +184,7 @@ const meta: Meta<INumberFieldProps> = {
errorMessage: undefined,
isDisabled: false,
isRequired: false,
direction: 'row',
},
};

Expand Down Expand Up @@ -302,3 +323,37 @@ export const CustomErrorMessage: Story = {
);
},
};

export const SmallWidth: Story = {
name: 'Small Width',
args: {
direction: 'column',
placeholder: '0',
label: 'Small Value',
},
render: (props) => {
return (
<div className={smallInputWrapper}>
<Form
className={formStorySmallInputsClass}
onSubmit={(e) => {
e.preventDefault();
}}
>
<NumberField
{...props}
label={`${props.label} 1`}
validationBehavior="aria"
minValue={0}
/>
<NumberField
{...props}
label={`${props.label} 2`}
validationBehavior="aria"
minValue={0}
/>
</Form>
</div>
);
},
};
Loading

0 comments on commit df0a2e8

Please sign in to comment.