Merge pull request #19366 from overleaf/ii-bs5-projects-list-search

[web] BS5 projects list search

GitOrigin-RevId: e2545f43ac3a50e58f7e97a2038e5b768c909e4f
This commit is contained in:
ilkin-overleaf 2024-07-16 13:40:21 +03:00 committed by Copybot
parent f54b257022
commit 0a23c55c93
15 changed files with 455 additions and 191 deletions

View file

@ -102,7 +102,7 @@ export default function CloneProjectModalContent({
{clonedProjectTags.length > 0 && ( {clonedProjectTags.length > 0 && (
<OLFormGroup <OLFormGroup
controlId="clone-project-tags-list" controlId="clone-project-tags-list"
className="clone-project-tag mb-3" className="clone-project-tag"
> >
<OLFormLabel>{t('tags')}: </OLFormLabel> <OLFormLabel>{t('tags')}: </OLFormLabel>
<div role="listbox" id="clone-project-tags-list"> <div role="listbox" id="clone-project-tags-list">

View file

@ -97,7 +97,6 @@ function ProjectListPageContent() {
filter={filter} filter={filter}
selectedTag={selectedTag} selectedTag={selectedTag}
className="overflow-hidden" className="overflow-hidden"
formGroupProps={{ className: 'mb-0' }}
/> />
</div> </div>
</div> </div>

View file

@ -1,36 +1,38 @@
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { import { FormControl } from 'react-bootstrap'
Form,
FormGroup,
FormGroupProps,
Col,
FormControl,
} from 'react-bootstrap'
import Icon from '../../../shared/components/icon' import Icon from '../../../shared/components/icon'
import * as eventTracking from '../../../infrastructure/event-tracking' import * as eventTracking from '../../../infrastructure/event-tracking'
import classnames from 'classnames' import classnames from 'classnames'
import { Tag } from '../../../../../app/src/Features/Tags/types' import { Tag } from '../../../../../app/src/Features/Tags/types'
import { MergeAndOverride } from '../../../../../types/utils'
import { Filter } from '../context/project-list-context' import { Filter } from '../context/project-list-context'
import { isSmallDevice } from '../../../infrastructure/event-tracking' import { isSmallDevice } from '../../../infrastructure/event-tracking'
import OLForm from '@/features/ui/components/ol/ol-form'
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
import OLCol from '@/features/ui/components/ol/ol-col'
import OLFormControl from '@/features/ui/components/ol/ol-form-control'
import MaterialIcon from '@/shared/components/material-icon'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { bsVersion } from '@/features/utils/bootstrap-5'
type SearchFormOwnProps = { type SearchFormOwnProps = {
inputValue: string inputValue: string
setInputValue: (input: string) => void setInputValue: (input: string) => void
filter: Filter filter: Filter
selectedTag: Tag | undefined selectedTag: Tag | undefined
formGroupProps?: FormGroupProps &
Omit<React.ComponentProps<'div'>, keyof FormGroupProps>
} }
type SearchFormProps = SearchFormOwnProps & type SearchFormProps = MergeAndOverride<
Omit<React.ComponentProps<typeof Form>, keyof SearchFormOwnProps> React.ComponentProps<typeof OLForm>,
SearchFormOwnProps
>
function SearchForm({ function SearchForm({
inputValue, inputValue,
setInputValue, setInputValue,
filter, filter,
selectedTag, selectedTag,
formGroupProps, className,
...props ...props
}: SearchFormProps) { }: SearchFormProps) {
const { t } = useTranslation() const { t } = useTranslation()
@ -57,8 +59,6 @@ function SearchForm({
} }
} }
const placeholder = `${placeholderMessage}` const placeholder = `${placeholderMessage}`
const { className: formGroupClassName, ...restFormGroupProps } =
formGroupProps || {}
const handleChange = ( const handleChange = (
e: React.ChangeEvent< e: React.ChangeEvent<
@ -75,44 +75,49 @@ function SearchForm({
const handleClear = () => setInputValue('') const handleClear = () => setInputValue('')
return ( return (
<Form <OLForm
horizontal className={classnames('project-search', className)}
className="project-search"
role="search" role="search"
onSubmit={e => e.preventDefault()} onSubmit={e => e.preventDefault()}
bs3Props={{ horizontal: true }}
{...props} {...props}
> >
<FormGroup <OLFormGroup
className={classnames( className={bsVersion({ bs3: 'has-feedback has-feedback-left' })}
'has-feedback has-feedback-left',
formGroupClassName
)}
{...restFormGroupProps}
> >
<Col xs={12}> <OLCol>
<FormControl <OLFormControl
type="text" type="text"
value={inputValue} value={inputValue}
onChange={handleChange} onChange={handleChange}
placeholder={placeholder} placeholder={placeholder}
aria-label={placeholder} aria-label={placeholder}
/> prepend={BootstrapVersionSwitcher({
<Icon type="search" className="form-control-feedback-left" /> bs3: <Icon type="search" />,
{inputValue.length ? ( bs5: <MaterialIcon type="search" />,
<div className="form-control-feedback"> })}
append={
inputValue.length > 0 && (
<button <button
type="button" type="button"
className="project-search-clear-btn btn-link" className={bsVersion({
bs3: 'project-search-clear-btn btn-link',
bs5: 'project-search-clear-btn',
})}
aria-label={t('clear_search')} aria-label={t('clear_search')}
onClick={handleClear} onClick={handleClear}
> >
<Icon type="times" /> <BootstrapVersionSwitcher
bs3={<Icon type="times" />}
bs5={<MaterialIcon type="clear" />}
/>
</button> </button>
</div> )
) : null} }
</Col> />
</FormGroup> </OLCol>
</Form> </OLFormGroup>
</OLForm>
) )
} }

View file

@ -0,0 +1,26 @@
import {
FormControl as BS3FormControl,
FormControlProps as BS3FormControlProps,
} from 'react-bootstrap'
type FormControlProps = BS3FormControlProps & {
prepend?: React.ReactNode
append?: React.ReactNode
}
function FormControl({
prepend,
append,
className,
...props
}: FormControlProps) {
return (
<>
{prepend && <div className="form-control-feedback-left">{prepend}</div>}
<BS3FormControl {...props} />
{append && <div className="form-control-feedback">{append}</div>}
</>
)
}
export default FormControl

View file

@ -0,0 +1,44 @@
import { forwardRef } from 'react'
import { Form, FormControlProps } from 'react-bootstrap-5'
import classnames from 'classnames'
type OLFormControlProps = FormControlProps & {
prepend?: React.ReactNode
append?: React.ReactNode
}
const FormControl = forwardRef<HTMLInputElement, OLFormControlProps>(
({ prepend, append, className, ...props }, ref) => {
if (prepend || append) {
const wrapperClassNames = classnames('form-control-wrapper', {
'form-control-wrapper-sm': props.size === 'sm',
'form-control-wrapper-lg': props.size === 'lg',
'form-control-wrapper-disabled': props.disabled,
})
const formControlClassNames = classnames(className, {
'form-control-offset-start': prepend,
'form-control-offset-end': append,
})
return (
<div className={wrapperClassNames}>
{prepend && (
<span className="form-control-start-icon">{prepend}</span>
)}
<Form.Control
{...props}
className={formControlClassNames}
ref={ref}
/>
{append && <span className="form-control-end-icon">{append}</span>}
</div>
)
}
return <Form.Control {...props} />
}
)
FormControl.displayName = 'FormControl'
export default FormControl

View file

@ -0,0 +1,14 @@
import { forwardRef } from 'react'
import { FormGroup as BS5FormGroup, FormGroupProps } from 'react-bootstrap-5'
import classnames from 'classnames'
const FormGroup = forwardRef<typeof BS5FormGroup, FormGroupProps>(
({ className, ...props }, ref) => {
const classNames = classnames('form-group', className)
return <BS5FormGroup className={classNames} {...props} ref={ref} />
}
)
FormGroup.displayName = 'FormGroup'
export default FormGroup

View file

@ -1,11 +1,12 @@
import { forwardRef } from 'react' import { forwardRef } from 'react'
import { Form } from 'react-bootstrap-5'
import { FormControl as BS3FormControl } from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5' import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
import BS3FormControl from '@/features/ui/components/bootstrap-3/form/form-control'
import FormControl from '@/features/ui/components/bootstrap-5/form/form-control'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
type OLFormControlProps = React.ComponentProps<(typeof Form)['Control']> & { type OLFormControlProps = React.ComponentProps<typeof FormControl> & {
bs3Props?: Record<string, unknown> bs3Props?: Record<string, unknown>
'data-ol-dirty'?: unknown
} }
const OLFormControl = forwardRef<HTMLInputElement, OLFormControlProps>( const OLFormControl = forwardRef<HTMLInputElement, OLFormControlProps>(
@ -37,6 +38,8 @@ const OLFormControl = forwardRef<HTMLInputElement, OLFormControlProps>(
ref.current = inputElement ref.current = inputElement
} }
}, },
prepend: rest.prepend,
append: rest.append,
...bs3Props, ...bs3Props,
} }
@ -49,7 +52,7 @@ const OLFormControl = forwardRef<HTMLInputElement, OLFormControlProps>(
return ( return (
<BootstrapVersionSwitcher <BootstrapVersionSwitcher
bs3={<BS3FormControl {...bs3FormControlProps} />} bs3={<BS3FormControl {...bs3FormControlProps} />}
bs5={<Form.Control ref={ref} {...rest} />} bs5={<FormControl ref={ref} {...rest} />}
/> />
) )
} }

View file

@ -1,27 +1,26 @@
import { Form } from 'react-bootstrap-5' import { FormGroupProps } from 'react-bootstrap-5'
import { FormGroup as BS3FormGroup } from 'react-bootstrap' import { FormGroup as BS3FormGroup } from 'react-bootstrap'
import FormGroup from '@/features/ui/components/bootstrap-5/form/form-group'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher' import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
type OLFormGroupProps = React.ComponentProps<(typeof Form)['Group']> & { type OLFormGroupProps = FormGroupProps & {
bs3Props?: Record<string, unknown> bs3Props?: Record<string, unknown>
} }
function OLFormGroup(props: OLFormGroupProps) { function OLFormGroup(props: OLFormGroupProps) {
const { bs3Props, className, ...rest } = props const { bs3Props, ...rest } = props
const classNames = className ?? 'mb-3'
const bs3FormGroupProps: React.ComponentProps<typeof BS3FormGroup> = { const bs3FormGroupProps: React.ComponentProps<typeof BS3FormGroup> = {
children: rest.children, children: rest.children,
controlId: rest.controlId, controlId: rest.controlId,
className, className: rest.className,
...bs3Props, ...bs3Props,
} }
return ( return (
<BootstrapVersionSwitcher <BootstrapVersionSwitcher
bs3={<BS3FormGroup {...bs3FormGroupProps} />} bs3={<BS3FormGroup {...bs3FormGroupProps} />}
bs5={<Form.Group className={classNames} {...rest} />} bs5={<FormGroup {...rest} />}
/> />
) )
} }

View file

@ -11,10 +11,11 @@ function OLForm(props: OLFormProps) {
const bs3FormProps: React.ComponentProps<typeof BS3Form> = { const bs3FormProps: React.ComponentProps<typeof BS3Form> = {
componentClass: rest.as, componentClass: rest.as,
bsClass: rest.className,
children: rest.children, children: rest.children,
id: rest.id, id: rest.id,
onSubmit: rest.onSubmit as React.FormEventHandler<BS3Form> | undefined, onSubmit: rest.onSubmit as React.FormEventHandler<BS3Form> | undefined,
className: rest.className,
role: rest.role,
...bs3Props, ...bs3Props,
} }

View file

@ -1,39 +1,42 @@
import { Form } from 'react-bootstrap-5' import { Form } from 'react-bootstrap-5'
import type { Meta, StoryObj } from '@storybook/react' import type { Meta, StoryObj } from '@storybook/react'
import FormGroup from '@/features/ui/components/bootstrap-5/form/form-group'
import FormText from '@/features/ui/components/bootstrap-5/form/form-text' import FormText from '@/features/ui/components/bootstrap-5/form/form-text'
import FormControl from '@/features/ui/components/bootstrap-5/form/form-control'
import MaterialIcon from '@/shared/components/material-icon'
const meta: Meta<(typeof Form)['Control']> = { const meta: Meta<React.ComponentProps<typeof FormControl>> = {
title: 'Shared / Components / Bootstrap 5 / Form / Input', title: 'Shared / Components / Bootstrap 5 / Form / Input',
component: Form.Control, component: FormControl,
parameters: { parameters: {
bootstrap5: true, bootstrap5: true,
}, },
} }
export default meta export default meta
type Story = StoryObj<(typeof Form)['Control']> type Story = StoryObj<React.ComponentProps<typeof FormControl>>
export const Default: Story = { export const Default: Story = {
render: args => { render: args => {
return ( return (
<> <>
<Form.Group controlId="id-1"> <FormGroup controlId="id-1">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control defaultValue="Large input" size="lg" {...args} /> <FormControl defaultValue="Large input" size="lg" {...args} />
<FormText>Helper</FormText> <FormText>Helper</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-2"> <FormGroup controlId="id-2">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control defaultValue="Regular input" {...args} /> <FormControl defaultValue="Regular input" {...args} />
<FormText>Helper</FormText> <FormText>Helper</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-3"> <FormGroup controlId="id-3">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control defaultValue="Small input" size="sm" {...args} /> <FormControl defaultValue="Small input" size="sm" {...args} />
<FormText>Helper</FormText> <FormText>Helper</FormText>
</Form.Group> </FormGroup>
</> </>
) )
}, },
@ -46,37 +49,37 @@ export const Info: Story = {
render: args => { render: args => {
return ( return (
<> <>
<Form.Group controlId="id-1"> <FormGroup controlId="id-1">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Large input" defaultValue="Large input"
size="lg" size="lg"
{...args} {...args}
/> />
<FormText isInfo>Info</FormText> <FormText isInfo>Info</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-2"> <FormGroup controlId="id-2">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Regular input" defaultValue="Regular input"
{...args} {...args}
/> />
<FormText isInfo>Info</FormText> <FormText isInfo>Info</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-3"> <FormGroup controlId="id-3">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Small input" defaultValue="Small input"
size="sm" size="sm"
{...args} {...args}
/> />
<FormText isInfo>Info</FormText> <FormText isInfo>Info</FormText>
</Form.Group> </FormGroup>
</> </>
) )
}, },
@ -86,9 +89,9 @@ export const Error: Story = {
render: args => { render: args => {
return ( return (
<> <>
<Form.Group controlId="id-1"> <FormGroup controlId="id-1">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Large input" defaultValue="Large input"
size="lg" size="lg"
@ -96,22 +99,22 @@ export const Error: Story = {
{...args} {...args}
/> />
<FormText isError>Error</FormText> <FormText isError>Error</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-2"> <FormGroup controlId="id-2">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Regular input" defaultValue="Regular input"
isInvalid isInvalid
{...args} {...args}
/> />
<FormText isError>Error</FormText> <FormText isError>Error</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-3"> <FormGroup controlId="id-3">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Small input" defaultValue="Small input"
size="sm" size="sm"
@ -119,7 +122,7 @@ export const Error: Story = {
{...args} {...args}
/> />
<FormText isError>Error</FormText> <FormText isError>Error</FormText>
</Form.Group> </FormGroup>
</> </>
) )
}, },
@ -129,37 +132,37 @@ export const Warning: Story = {
render: args => { render: args => {
return ( return (
<> <>
<Form.Group controlId="id-1"> <FormGroup controlId="id-1">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Large input" defaultValue="Large input"
size="lg" size="lg"
{...args} {...args}
/> />
<FormText isWarning>Warning</FormText> <FormText isWarning>Warning</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-2"> <FormGroup controlId="id-2">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Regular input" defaultValue="Regular input"
{...args} {...args}
/> />
<FormText isWarning>Warning</FormText> <FormText isWarning>Warning</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-3"> <FormGroup controlId="id-3">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Small input" defaultValue="Small input"
size="sm" size="sm"
{...args} {...args}
/> />
<FormText isWarning>Warning</FormText> <FormText isWarning>Warning</FormText>
</Form.Group> </FormGroup>
</> </>
) )
}, },
@ -169,37 +172,130 @@ export const Success: Story = {
render: args => { render: args => {
return ( return (
<> <>
<Form.Group controlId="id-1"> <FormGroup controlId="id-1">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Large input" defaultValue="Large input"
size="lg" size="lg"
{...args} {...args}
/> />
<FormText isSuccess>Success</FormText> <FormText isSuccess>Success</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-2"> <FormGroup controlId="id-2">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Regular input" defaultValue="Regular input"
{...args} {...args}
/> />
<FormText isSuccess>Success</FormText> <FormText isSuccess>Success</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-3"> <FormGroup controlId="id-3">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Small input" defaultValue="Small input"
size="sm" size="sm"
{...args} {...args}
/> />
<FormText isSuccess>Success</FormText> <FormText isSuccess>Success</FormText>
</Form.Group> </FormGroup>
</>
)
},
}
export const WithIcons: Story = {
render: args => {
const handleClear = () => {
alert('Clicked clear button')
}
return (
<>
<FormGroup controlId="id-1">
<Form.Label>Label</Form.Label>
<FormControl
type="text"
placeholder="Search"
prepend={<MaterialIcon type="search" />}
append={
<button
type="button"
className="project-search-clear-btn"
onClick={handleClear}
>
<MaterialIcon type="clear" />
</button>
}
size="lg"
{...args}
/>
</FormGroup>
<hr />
<FormGroup controlId="id-2">
<Form.Label>Label</Form.Label>
<FormControl
type="text"
placeholder="Search"
prepend={<MaterialIcon type="search" />}
append={
<button
type="button"
className="project-search-clear-btn"
onClick={handleClear}
>
<MaterialIcon type="clear" />
</button>
}
{...args}
/>
</FormGroup>
<hr />
<FormGroup controlId="id-3">
<Form.Label>Label</Form.Label>
<FormControl
type="text"
placeholder="Search"
prepend={<MaterialIcon type="search" />}
append={
<button
type="button"
className="project-search-clear-btn"
onClick={handleClear}
>
<MaterialIcon type="clear" />
</button>
}
size="sm"
{...args}
/>
</FormGroup>
<br />
<hr />
<FormGroup controlId="id-3">
<Form.Label>Disabled state</Form.Label>
<FormControl
type="text"
placeholder="Search"
prepend={<MaterialIcon type="search" />}
append={
<button
type="button"
className="project-search-clear-btn"
onClick={handleClear}
disabled
>
<MaterialIcon type="clear" />
</button>
}
disabled
{...args}
/>
</FormGroup>
</> </>
) )
}, },

View file

@ -1,8 +1,9 @@
import { Form } from 'react-bootstrap-5' import { Form, FormSelectProps } from 'react-bootstrap-5'
import type { Meta, StoryObj } from '@storybook/react' import type { Meta, StoryObj } from '@storybook/react'
import FormGroup from '@/features/ui/components/bootstrap-5/form/form-group'
import FormText from '@/features/ui/components/bootstrap-5/form/form-text' import FormText from '@/features/ui/components/bootstrap-5/form/form-text'
const meta: Meta<(typeof Form)['Select']> = { const meta: Meta<FormSelectProps> = {
title: 'Shared / Components / Bootstrap 5 / Form / Select', title: 'Shared / Components / Bootstrap 5 / Form / Select',
component: Form.Select, component: Form.Select,
parameters: { parameters: {
@ -11,13 +12,13 @@ const meta: Meta<(typeof Form)['Select']> = {
} }
export default meta export default meta
type Story = StoryObj<(typeof Form)['Select']> type Story = StoryObj<FormSelectProps>
export const Default: Story = { export const Default: Story = {
render: args => { render: args => {
return ( return (
<> <>
<Form.Group controlId="id-1"> <FormGroup controlId="id-1">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Select size="lg" {...args}> <Form.Select size="lg" {...args}>
<option>Large select</option> <option>Large select</option>
@ -26,9 +27,9 @@ export const Default: Story = {
<option value="3">Three</option> <option value="3">Three</option>
</Form.Select> </Form.Select>
<FormText>Helper</FormText> <FormText>Helper</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-2"> <FormGroup controlId="id-2">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Select {...args}> <Form.Select {...args}>
<option>Regular select</option> <option>Regular select</option>
@ -37,9 +38,9 @@ export const Default: Story = {
<option value="3">Three</option> <option value="3">Three</option>
</Form.Select> </Form.Select>
<FormText>Helper</FormText> <FormText>Helper</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-3"> <FormGroup controlId="id-3">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Select size="sm" {...args}> <Form.Select size="sm" {...args}>
<option>Small select</option> <option>Small select</option>
@ -48,7 +49,7 @@ export const Default: Story = {
<option value="3">Three</option> <option value="3">Three</option>
</Form.Select> </Form.Select>
<FormText>Helper</FormText> <FormText>Helper</FormText>
</Form.Group> </FormGroup>
</> </>
) )
}, },
@ -61,7 +62,7 @@ export const Info: Story = {
render: args => { render: args => {
return ( return (
<> <>
<Form.Group controlId="id-1"> <FormGroup controlId="id-1">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Select size="lg" {...args}> <Form.Select size="lg" {...args}>
<option>Large select</option> <option>Large select</option>
@ -70,9 +71,9 @@ export const Info: Story = {
<option value="3">Three</option> <option value="3">Three</option>
</Form.Select> </Form.Select>
<FormText isInfo>Info</FormText> <FormText isInfo>Info</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-2"> <FormGroup controlId="id-2">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Select {...args}> <Form.Select {...args}>
<option>Regular select</option> <option>Regular select</option>
@ -81,9 +82,9 @@ export const Info: Story = {
<option value="3">Three</option> <option value="3">Three</option>
</Form.Select> </Form.Select>
<FormText isInfo>Info</FormText> <FormText isInfo>Info</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-3"> <FormGroup controlId="id-3">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Select size="sm" {...args}> <Form.Select size="sm" {...args}>
<option>Small select</option> <option>Small select</option>
@ -92,7 +93,7 @@ export const Info: Story = {
<option value="3">Three</option> <option value="3">Three</option>
</Form.Select> </Form.Select>
<FormText isInfo>Info</FormText> <FormText isInfo>Info</FormText>
</Form.Group> </FormGroup>
</> </>
) )
}, },
@ -102,7 +103,7 @@ export const Error: Story = {
render: args => { render: args => {
return ( return (
<> <>
<Form.Group controlId="id-1"> <FormGroup controlId="id-1">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Select size="lg" isInvalid {...args}> <Form.Select size="lg" isInvalid {...args}>
<option>Large select</option> <option>Large select</option>
@ -111,9 +112,9 @@ export const Error: Story = {
<option value="3">Three</option> <option value="3">Three</option>
</Form.Select> </Form.Select>
<FormText isError>Error</FormText> <FormText isError>Error</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-2"> <FormGroup controlId="id-2">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Select isInvalid {...args}> <Form.Select isInvalid {...args}>
<option>Regular select</option> <option>Regular select</option>
@ -122,9 +123,9 @@ export const Error: Story = {
<option value="3">Three</option> <option value="3">Three</option>
</Form.Select> </Form.Select>
<FormText isError>Error</FormText> <FormText isError>Error</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-3"> <FormGroup controlId="id-3">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Select size="sm" isInvalid {...args}> <Form.Select size="sm" isInvalid {...args}>
<option>Small select</option> <option>Small select</option>
@ -133,7 +134,7 @@ export const Error: Story = {
<option value="3">Three</option> <option value="3">Three</option>
</Form.Select> </Form.Select>
<FormText isError>Error</FormText> <FormText isError>Error</FormText>
</Form.Group> </FormGroup>
</> </>
) )
}, },
@ -143,7 +144,7 @@ export const Warning: Story = {
render: args => { render: args => {
return ( return (
<> <>
<Form.Group controlId="id-1"> <FormGroup controlId="id-1">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Select size="lg" {...args}> <Form.Select size="lg" {...args}>
<option>Large select</option> <option>Large select</option>
@ -152,9 +153,9 @@ export const Warning: Story = {
<option value="3">Three</option> <option value="3">Three</option>
</Form.Select> </Form.Select>
<FormText isWarning>Warning</FormText> <FormText isWarning>Warning</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-2"> <FormGroup controlId="id-2">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Select {...args}> <Form.Select {...args}>
<option>Regular select</option> <option>Regular select</option>
@ -163,9 +164,9 @@ export const Warning: Story = {
<option value="3">Three</option> <option value="3">Three</option>
</Form.Select> </Form.Select>
<FormText isWarning>Warning</FormText> <FormText isWarning>Warning</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-3"> <FormGroup controlId="id-3">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Select size="sm" {...args}> <Form.Select size="sm" {...args}>
<option>Small select</option> <option>Small select</option>
@ -174,7 +175,7 @@ export const Warning: Story = {
<option value="3">Three</option> <option value="3">Three</option>
</Form.Select> </Form.Select>
<FormText isWarning>Warning</FormText> <FormText isWarning>Warning</FormText>
</Form.Group> </FormGroup>
</> </>
) )
}, },
@ -184,38 +185,38 @@ export const Success: Story = {
render: args => { render: args => {
return ( return (
<> <>
<Form.Group controlId="id-1"> <FormGroup controlId="id-1">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Select size="lg" isSuccess {...args}> <Form.Select size="lg" {...args}>
<option>Large select</option> <option>Large select</option>
<option value="1">One</option> <option value="1">One</option>
<option value="2">Two</option> <option value="2">Two</option>
<option value="3">Three</option> <option value="3">Three</option>
</Form.Select> </Form.Select>
<FormText isSuccess>Success</FormText> <FormText isSuccess>Success</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-2"> <FormGroup controlId="id-2">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Select isSuccess {...args}> <Form.Select {...args}>
<option>Regular select</option> <option>Regular select</option>
<option value="1">One</option> <option value="1">One</option>
<option value="2">Two</option> <option value="2">Two</option>
<option value="3">Three</option> <option value="3">Three</option>
</Form.Select> </Form.Select>
<FormText isSuccess>Success</FormText> <FormText isSuccess>Success</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-3"> <FormGroup controlId="id-3">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Select size="sm" isSuccess {...args}> <Form.Select size="sm" {...args}>
<option>Small select</option> <option>Small select</option>
<option value="1">One</option> <option value="1">One</option>
<option value="2">Two</option> <option value="2">Two</option>
<option value="3">Three</option> <option value="3">Three</option>
</Form.Select> </Form.Select>
<FormText isSuccess>Success</FormText> <FormText isSuccess>Success</FormText>
</Form.Group> </FormGroup>
</> </>
) )
}, },

View file

@ -1,49 +1,51 @@
import { Form } from 'react-bootstrap-5' import { Form } from 'react-bootstrap-5'
import type { Meta, StoryObj } from '@storybook/react' import type { Meta, StoryObj } from '@storybook/react'
import FormGroup from '@/features/ui/components/bootstrap-5/form/form-group'
import FormText from '@/features/ui/components/bootstrap-5/form/form-text' import FormText from '@/features/ui/components/bootstrap-5/form/form-text'
import FormControl from '@/features/ui/components/bootstrap-5/form/form-control'
const meta: Meta<(typeof Form)['Control']> = { const meta: Meta<React.ComponentProps<typeof FormControl>> = {
title: 'Shared / Components / Bootstrap 5 / Form / Textarea', title: 'Shared / Components / Bootstrap 5 / Form / Textarea',
component: Form.Control, component: FormControl,
parameters: { parameters: {
bootstrap5: true, bootstrap5: true,
}, },
} }
export default meta export default meta
type Story = StoryObj<(typeof Form)['Control']> type Story = StoryObj<React.ComponentProps<typeof FormControl>>
export const Default: Story = { export const Default: Story = {
render: args => { render: args => {
return ( return (
<> <>
<Form.Group controlId="id-1"> <FormGroup controlId="id-1">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
as="textarea" as="textarea"
defaultValue="Large input" defaultValue="Large input"
size="lg" size="lg"
{...args} {...args}
/> />
<FormText>Helper</FormText> <FormText>Helper</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-2"> <FormGroup controlId="id-2">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control as="textarea" defaultValue="Regular input" {...args} /> <FormControl as="textarea" defaultValue="Regular input" {...args} />
<FormText>Helper</FormText> <FormText>Helper</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-3"> <FormGroup controlId="id-3">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
as="textarea" as="textarea"
defaultValue="Small input" defaultValue="Small input"
size="sm" size="sm"
{...args} {...args}
/> />
<FormText>Helper</FormText> <FormText>Helper</FormText>
</Form.Group> </FormGroup>
</> </>
) )
}, },
@ -56,9 +58,9 @@ export const Info: Story = {
render: args => { render: args => {
return ( return (
<> <>
<Form.Group controlId="id-1"> <FormGroup controlId="id-1">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
as="textarea" as="textarea"
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Large input" defaultValue="Large input"
@ -66,22 +68,22 @@ export const Info: Story = {
{...args} {...args}
/> />
<FormText isInfo>Info</FormText> <FormText isInfo>Info</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-2"> <FormGroup controlId="id-2">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
as="textarea" as="textarea"
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Regular input" defaultValue="Regular input"
{...args} {...args}
/> />
<FormText isInfo>Info</FormText> <FormText isInfo>Info</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-3"> <FormGroup controlId="id-3">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
as="textarea" as="textarea"
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Small input" defaultValue="Small input"
@ -89,7 +91,7 @@ export const Info: Story = {
{...args} {...args}
/> />
<FormText isInfo>Info</FormText> <FormText isInfo>Info</FormText>
</Form.Group> </FormGroup>
</> </>
) )
}, },
@ -99,9 +101,9 @@ export const Error: Story = {
render: args => { render: args => {
return ( return (
<> <>
<Form.Group controlId="id-1"> <FormGroup controlId="id-1">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
as="textarea" as="textarea"
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Large input" defaultValue="Large input"
@ -110,11 +112,11 @@ export const Error: Story = {
{...args} {...args}
/> />
<FormText isError>Error</FormText> <FormText isError>Error</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-2"> <FormGroup controlId="id-2">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
as="textarea" as="textarea"
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Regular input" defaultValue="Regular input"
@ -122,11 +124,11 @@ export const Error: Story = {
{...args} {...args}
/> />
<FormText isError>Error</FormText> <FormText isError>Error</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-3"> <FormGroup controlId="id-3">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
as="textarea" as="textarea"
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Small input" defaultValue="Small input"
@ -135,7 +137,7 @@ export const Error: Story = {
{...args} {...args}
/> />
<FormText isError>Error</FormText> <FormText isError>Error</FormText>
</Form.Group> </FormGroup>
</> </>
) )
}, },
@ -145,9 +147,9 @@ export const Warning: Story = {
render: args => { render: args => {
return ( return (
<> <>
<Form.Group controlId="id-1"> <FormGroup controlId="id-1">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
as="textarea" as="textarea"
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Large input" defaultValue="Large input"
@ -155,22 +157,22 @@ export const Warning: Story = {
{...args} {...args}
/> />
<FormText isWarning>Warning</FormText> <FormText isWarning>Warning</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-2"> <FormGroup controlId="id-2">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
as="textarea" as="textarea"
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Regular input" defaultValue="Regular input"
{...args} {...args}
/> />
<FormText isWarning>Warning</FormText> <FormText isWarning>Warning</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-3"> <FormGroup controlId="id-3">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
as="textarea" as="textarea"
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Small input" defaultValue="Small input"
@ -178,7 +180,7 @@ export const Warning: Story = {
{...args} {...args}
/> />
<FormText isWarning>Warning</FormText> <FormText isWarning>Warning</FormText>
</Form.Group> </FormGroup>
</> </>
) )
}, },
@ -188,9 +190,9 @@ export const Success: Story = {
render: args => { render: args => {
return ( return (
<> <>
<Form.Group controlId="id-1"> <FormGroup controlId="id-1">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
as="textarea" as="textarea"
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Large input" defaultValue="Large input"
@ -198,22 +200,22 @@ export const Success: Story = {
{...args} {...args}
/> />
<FormText isSuccess>Success</FormText> <FormText isSuccess>Success</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-2"> <FormGroup controlId="id-2">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
as="textarea" as="textarea"
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Regular input" defaultValue="Regular input"
{...args} {...args}
/> />
<FormText isSuccess>Success</FormText> <FormText isSuccess>Success</FormText>
</Form.Group> </FormGroup>
<hr /> <hr />
<Form.Group controlId="id-3"> <FormGroup controlId="id-3">
<Form.Label>Label</Form.Label> <Form.Label>Label</Form.Label>
<Form.Control <FormControl
as="textarea" as="textarea"
placeholder="Placeholder" placeholder="Placeholder"
defaultValue="Small input" defaultValue="Small input"
@ -221,7 +223,7 @@ export const Success: Story = {
{...args} {...args}
/> />
<FormText isSuccess>Success</FormText> <FormText isSuccess>Success</FormText>
</Form.Group> </FormGroup>
</> </>
) )
}, },

View file

@ -386,11 +386,9 @@ ul.folders-menu {
form.project-search { form.project-search {
.form-group { .form-group {
@media (min-width: @screen-md) {
margin-bottom: 0; margin-bottom: 0;
} }
} }
}
.project-search-clear-btn { .project-search-clear-btn {
width: 100%; width: 100%;

View file

@ -84,3 +84,69 @@
.form-group { .form-group {
margin-bottom: var(--spacing-06); margin-bottom: var(--spacing-06);
} }
.form-control-wrapper {
position: relative;
&.form-control-wrapper-disabled {
.form-control-start-icon,
.form-control-end-icon {
& > * {
color: var(--content-disabled);
}
}
}
.form-control-start-icon,
.form-control-end-icon {
position: absolute;
top: 0;
height: 100%;
display: flex;
align-items: center;
font-size: 0;
}
.form-control-start-icon {
left: 0;
}
.form-control-end-icon {
right: 0;
}
--icon-width: 20px;
--form-control-padding-x: var(--spacing-04);
--form-control-icon-offset-y: var(--spacing-04);
&.form-control-wrapper-sm {
--form-control-padding-x: var(--spacing-03);
--form-control-icon-offset-y: var(--spacing-02);
}
&.form-control-wrapper-lg {
--form-control-padding-x: var(--spacing-05);
--form-control-icon-offset-y: var(--spacing-05);
}
.form-control-start-icon {
padding-left: calc(var(--form-control-padding-x) + var(--bs-border-width));
}
.form-control-end-icon {
padding-right: calc(var(--form-control-padding-x) + var(--bs-border-width));
}
.form-control-offset-start {
padding-left: calc(
var(--form-control-padding-x) + var(--form-control-icon-offset-y) +
var(--icon-width)
);
}
.form-control-offset-end {
padding-right: calc(
var(--form-control-padding-x) + var(--form-control-icon-offset-y) +
var(--icon-width)
);
}
}

View file

@ -464,3 +464,13 @@
.project-list-load-more-button { .project-list-load-more-button {
margin-bottom: var(--spacing-05); margin-bottom: var(--spacing-05);
} }
form.project-search {
.form-group {
margin-bottom: 0;
}
}
.project-search-clear-btn {
@include reset-button;
}