Skip to content

Commit

Permalink
inline form support (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelcolvin authored Nov 28, 2023
1 parent 3cddb00 commit dc7461b
Show file tree
Hide file tree
Showing 11 changed files with 302 additions and 156 deletions.
126 changes: 60 additions & 66 deletions packages/fastui-bootstrap/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function displayPrimitiveRender(props: components.DisplayPrimitiveProps) {
}
}

export const classNameGenerator: ClassNameGenerator = ({ props, fullPath, subElement }) => {
export const classNameGenerator: ClassNameGenerator = ({ props, fullPath, subElement }): ClassName => {
const { type } = props
switch (type) {
case 'Page':
Expand All @@ -52,78 +52,72 @@ export const classNameGenerator: ClassNameGenerator = ({ props, fullPath, subEle
}
case 'Form':
case 'ModelForm':
return formClassName(subElement)
if (props.displayMode === 'inline') {
switch (subElement) {
case 'form-container':
return ''
default:
return 'row row-cols-lg-4 align-items-center justify-content-end'
}
} else {
switch (subElement) {
case 'form-container':
return 'row justify-content-center'
default:
return 'col-md-4'
}
}
case 'FormFieldInput':
case 'FormFieldCheckbox':
case 'FormFieldSelect':
case 'FormFieldSelectSearch':
case 'FormFieldFile':
return formFieldClassName(props, subElement)
switch (subElement) {
case 'input':
return props.error ? 'is-invalid form-control' : 'form-control'
case 'select':
return 'form-select'
case 'select-react':
return ''
case 'label':
if (props.displayMode === 'inline') {
return 'visually-hidden'
} else {
return { 'form-label': true, 'fw-bold': props.required }
}
case 'error':
return 'invalid-feedback'
case 'description':
return 'form-text'
default:
return 'mb-3'
}
case 'Navbar':
return navbarClassName(subElement)
switch (subElement) {
case 'contents':
return 'container'
case 'title':
return 'navbar-brand'
default:
return 'navbar navbar-expand-lg bg-body-tertiary'
}
case 'Link':
return linkClassName(props, fullPath)
return {
active: pathMatch(props.active, fullPath),
'nav-link': props.mode === 'navbar' || props.mode === 'tabs',
}
case 'LinkList':
return linkListClassName(props, subElement)
}
}

function formFieldClassName(props: components.FormFieldProps, subElement?: string): ClassName {
switch (subElement) {
case 'input':
return props.error ? 'is-invalid form-control' : 'form-control'
case 'select':
return 'form-select'
case 'select-react':
return ''
case 'label':
return { 'form-label': true, 'fw-bold': props.required }
case 'error':
return 'invalid-feedback'
case 'description':
return 'form-text'
default:
return 'mb-3'
}
}

function formClassName(subElement?: string): ClassName {
switch (subElement) {
case 'form-container':
return 'row justify-content-center'
default:
return 'col-md-4'
}
}

function navbarClassName(subElement?: string): ClassName {
switch (subElement) {
case 'contents':
return 'container'
case 'title':
return 'navbar-brand'
default:
return 'navbar navbar-expand-lg bg-body-tertiary'
}
}

function linkClassName(props: components.LinkProps, fullPath: string): ClassName {
return {
active: pathMatch(props.active, fullPath),
'nav-link': props.mode === 'navbar' || props.mode === 'tabs',
}
}

function linkListClassName(props: components.LinkListProps, subElement?: string): ClassName {
if (subElement === 'link-list-item' && props.mode) {
return 'nav-item'
}
switch (props.mode) {
case 'tabs':
return 'nav nav-underline'
case 'vertical':
return 'nav flex-column'
default:
return ''
if (subElement === 'link-list-item' && props.mode) {
return 'nav-item'
} else {
switch (props.mode) {
case 'tabs':
return 'nav nav-underline'
case 'vertical':
return 'nav flex-column'
default:
return ''
}
}
}
}
126 changes: 82 additions & 44 deletions packages/fastui/src/components/FormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ interface BaseFormFieldProps {
locked: boolean
error?: string
description?: string
displayMode?: 'default' | 'inline'
className?: ClassName
onChange?: () => void
}

export type FormFieldProps =
Expand Down Expand Up @@ -128,57 +130,82 @@ interface FormFieldSelectProps extends BaseFormFieldProps {
initial?: string
multiple?: boolean
vanilla?: boolean
placeholder?: string
}

export const FormFieldSelectComp: FC<FormFieldSelectProps> = (props) => {
const { name, required, locked, options, multiple, initial, vanilla } = props
if (props.vanilla) {
return <FormFieldSelectVanillaComp {...props} />
} else {
return <FormFieldSelectReactComp {...props} />
}
}

export const FormFieldSelectVanillaComp: FC<FormFieldSelectProps> = (props) => {
const { name, required, locked, options, multiple, initial, placeholder, onChange } = props

const className = useClassName(props)
const classNameSelect = useClassName(props, { el: 'select' })
return (
<div className={className}>
<Label {...props} />
<select
id={inputId(props)}
className={classNameSelect}
defaultValue={initial}
multiple={multiple}
name={name}
required={required}
disabled={locked}
aria-describedby={descId(props)}
placeholder={placeholder}
onChange={() => onChange && onChange()}
>
{multiple ? null : <option></option>}
{options.map((option, i) => (
<SelectOptionComp key={i} option={option} />
))}
</select>
<ErrorDescription {...props} />
</div>
)
}

export const FormFieldSelectReactComp: FC<FormFieldSelectProps> = (props) => {
const { name, required, locked, options, multiple, initial, placeholder, onChange } = props

const className = useClassName(props)
const classNameSelectReact = useClassName(props, { el: 'select-react' })
if (vanilla) {
return (
<div className={className}>
<Label {...props} />
<select
id={inputId(props)}
className={classNameSelect}
defaultValue={initial}
multiple={multiple}
name={name}
required={required}
disabled={locked}
aria-describedby={descId(props)}
>
{multiple ? null : <option></option>}
{options.map((option, i) => (
<SelectOptionComp key={i} option={option} />
))}
</select>
<ErrorDescription {...props} />
</div>
)
} else {
return (
<div className={className}>
<Label {...props} />
<Select
id={inputId(props)}
className={classNameSelectReact}
isMulti={multiple ?? false}
isClearable
defaultValue={findDefault(options, initial)}
name={name}
required={required}
isDisabled={locked}
options={options}
aria-describedby={descId(props)}
styles={styles}
/>
<ErrorDescription {...props} />
</div>
)

const reactSelectOnChanged = () => {
// TODO this is a hack to wait for the input to be updated, can we do better?
setTimeout(() => {
onChange && onChange()
}, 50)
}

return (
<div className={className}>
<Label {...props} />
<Select
id={inputId(props)}
classNamePrefix="fastui-react-select"
className={classNameSelectReact}
isMulti={multiple ?? false}
isClearable
defaultValue={findDefault(options, initial)}
name={name}
required={required}
isDisabled={locked}
options={options}
aria-describedby={descId(props)}
styles={styles}
placeholder={placeholder}
onChange={reactSelectOnChanged}
/>
<ErrorDescription {...props} />
</div>
)
}

const SelectOptionComp: FC<{ option: SelectOption | SelectGroup }> = ({ option }) => {
Expand Down Expand Up @@ -214,10 +241,11 @@ interface FormFieldSelectSearchProps extends BaseFormFieldProps {
debounce?: number
initial?: SelectOption
multiple?: boolean
placeholder?: string
}

export const FormFieldSelectSearchComp: FC<FormFieldSelectSearchProps> = (props) => {
const { name, required, locked, searchUrl, initial, multiple } = props
const { name, required, locked, searchUrl, initial, multiple, placeholder, onChange } = props
const [isLoading, setIsLoading] = useState(false)
const request = useRequest()

Expand All @@ -237,10 +265,18 @@ export const FormFieldSelectSearchComp: FC<FormFieldSelectSearchProps> = (props)
})
}, props.debounce ?? 300)

const reactSelectOnChanged = () => {
// TODO this is a hack to wait for the input to be updated, can we do better?
setTimeout(() => {
onChange && onChange()
}, 50)
}

return (
<div className={useClassName(props)}>
<Label {...props} />
<AsyncSelect
classNamePrefix="fastui-react-select"
id={inputId(props)}
className={useClassName(props, { el: 'select-react' })}
isMulti={multiple ?? false}
Expand All @@ -256,6 +292,8 @@ export const FormFieldSelectSearchComp: FC<FormFieldSelectSearchProps> = (props)
isLoading={isLoading}
aria-describedby={descId(props)}
styles={styles}
placeholder={placeholder}
onChange={reactSelectOnChanged}
/>
<ErrorDescription {...props} />
</div>
Expand Down
Loading

0 comments on commit dc7461b

Please sign in to comment.