diff --git a/.storybook/preview.js b/.storybook/preview.js index 59142b7..2d67384 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -2,7 +2,43 @@ import { Bluelib } from "../src/components/Bluelib" export const parameters = { - actions: { - argTypesRegex: "^on[A-Z][a-z]*$" + argTypes: { + bluelibClassNames: { + control: {type: "string"}, + description: "Additional Bluelib classNames to be appended to the element's classNames", + table: {category: "Global props"} + }, + customColor: { + control: {type: "color"}, + description: "Apply a Bluelib custom color to the element", + table: {category: "Global props"} + }, + disabled: { + control: {type: "boolean"}, + description: "Apply the disabled status to an element", + table: {category: "Global props"} + } + }, + options: { + storySort: { + order: [ + "Core", + "Layouts", + "Panels", + "Chapters", + "Separators", + "Images", + "Tables", + "Lists", + "Status", + "Inputs", + "Forms", + "Common", + "Annotations", + "Semantics", + "Colors", + "Internals", + ] + } }, } \ No newline at end of file diff --git a/package.json b/package.json index 4059f96..74bc34d 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,14 @@ "@types/node": "^12.0.0", "@types/react": "^17.0.0", "@types/react-dom": "^17.0.0", + "@types/uuid": "^8.3.1", "classnames": "^2.3.1", "color": "https://github.com/Steffo99/color", "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "4.0.3", "typescript": "^4.1.2", + "uuid": "^8.3.2", "web-vitals": "^1.0.1" }, "devDependencies": { diff --git a/src/bluelib b/src/bluelib index 0fe4c9e..48722dc 160000 --- a/src/bluelib +++ b/src/bluelib @@ -1 +1 @@ -Subproject commit 0fe4c9e477a681031c4d62fd9a7c39e94befa849 +Subproject commit 48722dcbfa0d6e9e1e5fe3bcb84b0be102d5db38 diff --git a/src/components/BaseElement.stories.jsx b/src/components/BaseElement.stories.jsx index fe1c55f..40cf948 100644 --- a/src/components/BaseElement.stories.jsx +++ b/src/components/BaseElement.stories.jsx @@ -1,42 +1,23 @@ import * as React from "react" import * as ReactDOM from "react-dom" import * as Decorators from "../utils/Decorators" -import { BaseElement } from "./BaseElement" +import { BaseElement as BaseElementComponent } from "./BaseElement" import { Bluelib } from "./Bluelib" export default { - component: BaseElement, - title: "Bluelib/BaseElement", + component: BaseElementComponent, + title: "Internals/Base Element", decorators: [Decorators.Bluelib], - argTypes: { - customColor: { - control: {type: "color"}, - }, - }, } -export const Default = props => ( - +export const BaseElement = props => ( + This is a text node child. - + ) -Default.args = { +BaseElement.args = { kind: "div", disabled: false, } - - -export const CustomColor = Default.bind({}) -CustomColor.args = { - ...Default.args, - customColor: "#ff7f00", -} - - -export const Disabled = Default.bind({}) -Disabled.args = { - ...Default.args, - disabled: true, -} \ No newline at end of file diff --git a/src/components/BaseElement.tsx b/src/components/BaseElement.tsx index 56feffc..c46ebc1 100644 --- a/src/components/BaseElement.tsx +++ b/src/components/BaseElement.tsx @@ -7,29 +7,28 @@ import Color from "color" import mergeClassNames, {Argument as ClassNamesArgument} from "classnames" -export interface BaseElementProps { - kind: Types.ComponentKind, +export interface BaseElementProps extends React.HTMLProps { + kind: string, bluelibClassNames?: Types.ClassNames, - customColor?: typeof Color, disabled?: boolean, - - [props: string]: any, + customColor?: typeof Color, } -export function BaseElement({kind, bluelibClassNames, customColor, ...props}: BaseElementProps): JSX.Element { +export function BaseElement({kind = "div", bluelibClassNames, disabled = false, customColor, ...props}: BaseElementProps): JSX.Element { // Set the Bluelib color - if(customColor) props.style = {...props.style, ...Colors.colorToBluelibStyle("color", customColor)} + if(customColor) { + props.style = {...props.style, ...Colors.colorToBluelibStyle("color", customColor)} + } // Possibly disable the element - if(props.disabled) bluelibClassNames = mergeClassNames(bluelibClassNames, "status-disabled") + bluelibClassNames = mergeClassNames(bluelibClassNames, disabled ? "status-disabled" : "") + // @ts-ignore + props.disabled = disabled // Map regular class names to module class names bluelibClassNames = BluelibMapper.rootToModule(bluelibClassNames) props.className = mergeClassNames(props.className, bluelibClassNames) - // Dynamically determine the element kind - const Kind = kind - - return + return React.createElement(kind, props) } diff --git a/src/components/Bluelib.stories.jsx b/src/components/Bluelib.stories.jsx index ef5654c..e09e1d0 100644 --- a/src/components/Bluelib.stories.jsx +++ b/src/components/Bluelib.stories.jsx @@ -7,7 +7,7 @@ import Color from "color" export default { component: Bluelib, - title: "Bluelib/Bluelib", + title: "Core/Bluelib", decorators: [Decorators.Fill], parameters: { layout: "fullscreen", diff --git a/src/components/Bluelib.tsx b/src/components/Bluelib.tsx index d479a5f..a87f86c 100644 --- a/src/components/Bluelib.tsx +++ b/src/components/Bluelib.tsx @@ -19,7 +19,7 @@ const BuiltinThemes = { } -export interface BluelibProps { +export interface BluelibProps extends Types.BluelibHTMLProps { theme: "paper" | "royalblue" | "hacker" | "sophon", backgroundColor?: typeof Color, @@ -38,8 +38,6 @@ export interface BluelibProps { magentaColor?: typeof Color, grayColor?: typeof Color, polarity?: number, - - [props: string]: any, } @@ -81,7 +79,10 @@ export function Bluelib({ if(blueColor) props.style = {...props.style, ...Colors.colorToBluelibStyle("blue", blueColor)} if(magentaColor) props.style = {...props.style, ...Colors.colorToBluelibStyle("magenta", magentaColor)} if(grayColor) props.style = {...props.style, ...Colors.colorToBluelibStyle("gray", grayColor)} - if(polarity) props.style["--bluelib-polarity"] = polarity + if(polarity) { + // @ts-ignore + props.style["--bluelib-polarity"] = polarity + } return ( ( +export const Basic = props => ( First Second Third ) -Default.args = { - disabled: false, -} +Basic.args = {} export const AutoWrap = props => ( @@ -46,14 +44,18 @@ export const AutoWrap = props => ( Tenth Eleventh Twelfth - Thirtheenth - Fourtheenth - Fiftheenth + Thirteenth + Fourteenth + Fifteenth + Sixteenth + Seventeenth + Eighteenth + Ninteenth + Ninteenth + Twentieth ) -AutoWrap.args = { - disabled: false, -} +AutoWrap.args = {} export const ForceWrap = props => ( @@ -65,6 +67,4 @@ export const ForceWrap = props => ( Fourth ) -ForceWrap.args = { - disabled: false, -} +ForceWrap.args = {} diff --git a/src/components/chapters/Chapter.tsx b/src/components/chapters/Chapter.tsx index 9abba97..c17e2a2 100644 --- a/src/components/chapters/Chapter.tsx +++ b/src/components/chapters/Chapter.tsx @@ -3,11 +3,10 @@ import * as ReactDOM from "react-dom" import * as Types from "../../types" import {BaseElement} from "../BaseElement" import mergeClassNames from "classnames" +import {ChapterForceWrap} from "./ChapterForceWrap"; -interface ChapterProps { - [props: string]: any, -} +export interface ChapterProps extends Types.BluelibHTMLProps {} export function Chapter({...props}: ChapterProps): JSX.Element { @@ -19,15 +18,4 @@ export function Chapter({...props}: ChapterProps): JSX.Element { } -interface ChapterForceWrapProps { - [props: string]: any, -} - - -Chapter.ForceWrap = function({...props}: ChapterForceWrapProps): JSX.Element { - props.bluelibClassNames = mergeClassNames(props.bluelibClassNames, "chapter-forcewrap") - - return ( - - ) -} \ No newline at end of file +Chapter.ForceWrap = ChapterForceWrap \ No newline at end of file diff --git a/src/components/chapters/ChapterForceWrap.tsx b/src/components/chapters/ChapterForceWrap.tsx new file mode 100644 index 0000000..621ceb8 --- /dev/null +++ b/src/components/chapters/ChapterForceWrap.tsx @@ -0,0 +1,17 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import * as Types from "../../types" +import {BaseElement} from "../BaseElement" +import mergeClassNames from "classnames" + + +export interface ChapterForceWrapProps extends Types.BluelibHTMLProps {} + + +export function ChapterForceWrap({...props}: ChapterForceWrapProps): JSX.Element { + props.bluelibClassNames = mergeClassNames(props.bluelibClassNames, "chapter-forcewrap") + + return ( + + ) +} diff --git a/src/components/forms/Form.stories.jsx b/src/components/forms/Form.stories.jsx new file mode 100644 index 0000000..5bb0239 --- /dev/null +++ b/src/components/forms/Form.stories.jsx @@ -0,0 +1,74 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import * as Decorators from "../../utils/Decorators" +import { Form as FormComponent } from "./Form" +import { FormField } from "./FormField" +import { FormArea } from "./FormArea" +import { FormSelect } from "./FormSelect" +import { Option } from "../inputs/Option" +import { FormMultiselect } from "./FormMultiselect" +import { FormRadioGroup } from "./FormRadioGroup" +import { FormCheckboxGroup } from "./FormCheckboxGroup" +import { FormRow } from "./FormRow" +import { Button } from "../inputs/Button" +import { Parenthesis } from "../panels/Parenthesis" + + +export default { + component: FormComponent, + subcomponents: {FormField, FormRow, FormArea, FormSelect, FormRadioGroup, FormCheckboxGroup, Parenthesis, Option, Button}, + title: "Forms/Form", + decorators: [Decorators.Box, Decorators.Bluelib], +} + + +export const Form = props => ( + + + + + Enter the details of your characters below. + + + + + + + + + + + + + Throw fireball + Shoot a magic missile + Save character + + +) +Form.args = {} diff --git a/src/components/forms/Form.tsx b/src/components/forms/Form.tsx new file mode 100644 index 0000000..4a833b2 --- /dev/null +++ b/src/components/forms/Form.tsx @@ -0,0 +1,44 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import * as Types from "../../types" +import {BaseElement} from "../BaseElement" +import mergeClassNames from "classnames" +import {FormArea} from "./FormArea"; +import {FormCheckboxGroup} from "./FormCheckboxGroup"; +import {FormField} from "./FormField"; +import {FormMultiselect} from "./FormMultiselect"; +import {FormRadioGroup} from "./FormRadioGroup"; +import {FormRow} from "./FormRow"; +import {FormSelect} from "./FormSelect"; +import {FormLabel} from "./FormLabel"; +import {FormPair} from "./FormPair"; +import {FormGroup} from "./FormGroup"; +import {Button} from "../inputs/Button"; + + +export interface FormProps extends Types.BluelibHTMLProps {} + + +export function Form({...props}: FormProps): JSX.Element { + props.bluelibClassNames = mergeClassNames(props.bluelibClassNames, "form") + + return ( + + ) +} + + +Form.Area = FormArea +Form.Checkboxes = FormCheckboxGroup +Form.Field = FormField +Form.Multiselect = FormMultiselect +Form.Radios = FormRadioGroup +Form.Row = FormRow +Form.Select = FormSelect +Form.Button = Button + +Form.Internals = { + Label: FormLabel, + Pair: FormPair, + Group: FormGroup, +} \ No newline at end of file diff --git a/src/components/forms/FormArea.stories.jsx b/src/components/forms/FormArea.stories.jsx new file mode 100644 index 0000000..2658647 --- /dev/null +++ b/src/components/forms/FormArea.stories.jsx @@ -0,0 +1,23 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import * as Decorators from "../../utils/Decorators" +import { FormArea as FormAreaComponent } from "./FormArea" + + +export default { + component: FormAreaComponent, + title: "Forms/Form Area", + decorators: [Decorators.Form, Decorators.Box, Decorators.Bluelib], + argTypes: { + onChange: {action: "Change"}, + onSimpleChange: {action: "SimpleChange"}, + }, +} + + +export const FormArea = props => ( + +) +FormArea.args = { + label: "Bio", +} diff --git a/src/components/forms/FormArea.tsx b/src/components/forms/FormArea.tsx new file mode 100644 index 0000000..d415d52 --- /dev/null +++ b/src/components/forms/FormArea.tsx @@ -0,0 +1,30 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import * as Types from "../../types" +import {BaseElement} from "../BaseElement" +import mergeClassNames from "classnames" +import {FormPair, FormPairProps} from "./FormPair"; +import {FormLabel, FormLabelProps} from "./FormLabel"; +import {Area, AreaProps} from "../inputs/Area"; + + +export interface FormAreaProps extends AreaProps { + label: string, + + validity?: Types.Validity, + + pairProps?: FormPairProps, + labelProps?: FormLabelProps, +} + + +export function FormArea({label, validity, pairProps, labelProps, ...props}: FormAreaProps): JSX.Element { + return ( + {label}} + input={} + validity={validity} + {...pairProps} + /> + ) +} diff --git a/src/components/forms/FormCheckboxGroup.stories.jsx b/src/components/forms/FormCheckboxGroup.stories.jsx new file mode 100644 index 0000000..5c58abb --- /dev/null +++ b/src/components/forms/FormCheckboxGroup.stories.jsx @@ -0,0 +1,34 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import * as Decorators from "../../utils/Decorators" +import { FormCheckboxGroup as FormCheckboxGroupComponent } from "./FormCheckboxGroup" + + +export default { + component: FormCheckboxGroupComponent, + title: "Forms/Form Checkbox Group", + decorators: [Decorators.Form, Decorators.Box, Decorators.Bluelib], + argTypes: { + onChange: {action: "Change"}, + onSimpleChange: {action: "SimpleChange"}, + }, +} + + +export const Unmanaged = props => ( + +) +Unmanaged.args = { + label: "Sizes", + options: ["XS", "S", "M", "L", "XL"], + value: undefined, + row: false, + disabled: false, +} + + +export const Managed = Unmanaged.bind({}) +Managed.args = { + ...Unmanaged.args, + value: ["M"], +} diff --git a/src/components/forms/FormCheckboxGroup.tsx b/src/components/forms/FormCheckboxGroup.tsx new file mode 100644 index 0000000..76ebeb3 --- /dev/null +++ b/src/components/forms/FormCheckboxGroup.tsx @@ -0,0 +1,83 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import * as Types from "../../types" +import * as UUID from "uuid" +import mergeClassNames from "classnames" +import {FormPair, FormPairProps} from "./FormPair"; +import {FormLabel, FormLabelProps} from "./FormLabel"; +import {FormGroup, FormGroupProps} from "./FormGroup"; +import {LabelledCheckbox, LabelledCheckboxProps} from "../inputs/LabelledCheckbox"; + + +export interface FormCheckboxGroupProps extends Types.BluelibProps { + name?: string, + label: string, + options: string[], + + row?: boolean, + + onChange?: (event: React.ChangeEvent) => void, + onSimpleChange?: (value: string[]) => void, + value?: string[], + + validity?: Types.Validity, + + pairProps?: FormPairProps, + labelProps?: FormLabelProps, + groupProps?: FormGroupProps, + checkboxProps?: LabelledCheckboxProps, +} + + +export function FormCheckboxGroup({name, label, options, row, onChange, onSimpleChange, value, validity, pairProps, labelProps, groupProps, checkboxProps, disabled, bluelibClassNames, customColor}: FormCheckboxGroupProps): JSX.Element { + if(!name) { + name = UUID.v4() + } + + const checkboxes = options.map(option => ( + + )) + + const onChangeWrapped = React.useCallback( + event => { + if(onChange) onChange(event) + if(value && onSimpleChange) { + if(event.target.checked) { + onSimpleChange(value.concat(event.target.value)) + } + else { + const valueCopy = Array.from(value) + const indexOf = valueCopy.indexOf(event.target.value) + if(indexOf !== -1) valueCopy.splice(indexOf, 1) + onSimpleChange(valueCopy) + } + } + }, + [onChange, value, onSimpleChange] + ) + + const group = ( + + {checkboxes} + + ) + + return ( + {label}} + input={group} + validity={validity} + bluelibClassNames={bluelibClassNames} + customColor={customColor} + {...pairProps} + /> + ) +} diff --git a/src/components/forms/FormField.stories.jsx b/src/components/forms/FormField.stories.jsx new file mode 100644 index 0000000..967a87b --- /dev/null +++ b/src/components/forms/FormField.stories.jsx @@ -0,0 +1,23 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import * as Decorators from "../../utils/Decorators" +import { FormField as FormFieldComponent } from "./FormField" + + +export default { + component: FormFieldComponent, + title: "Forms/Form Field", + decorators: [Decorators.Form, Decorators.Box, Decorators.Bluelib], + argTypes: { + onChange: {action: "Change"}, + onSimpleChange: {action: "SimpleChange"}, + }, +} + + +export const FormField = props => ( + +) +FormField.args = { + label: "Username", +} diff --git a/src/components/forms/FormField.tsx b/src/components/forms/FormField.tsx new file mode 100644 index 0000000..b9aa0bc --- /dev/null +++ b/src/components/forms/FormField.tsx @@ -0,0 +1,30 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import * as Types from "../../types" +import {BaseElement} from "../BaseElement" +import mergeClassNames from "classnames" +import {FormPair, FormPairProps} from "./FormPair"; +import {FormLabel, FormLabelProps} from "./FormLabel"; +import {Field, FieldProps} from "../inputs/Field"; + + +export interface FormFieldProps extends FieldProps { + label: string, + + validity?: Types.Validity, + + pairProps?: FormPairProps, + labelProps?: FormLabelProps, +} + + +export function FormField({label, validity, pairProps, labelProps, ...props}: FormFieldProps): JSX.Element { + return ( + {label}} + input={} + validity={validity} + {...pairProps} + /> + ) +} diff --git a/src/components/forms/FormGroup.tsx b/src/components/forms/FormGroup.tsx new file mode 100644 index 0000000..d9d0399 --- /dev/null +++ b/src/components/forms/FormGroup.tsx @@ -0,0 +1,17 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import * as Types from "../../types" +import {BaseElement} from "../BaseElement" +import mergeClassNames from "classnames" + + +export interface FormGroupProps extends Types.BluelibHTMLProps {} + + +export function FormGroup({...props}: FormGroupProps): JSX.Element { + props.bluelibClassNames = mergeClassNames(props.bluelibClassNames, "form-group") + + return ( + + ) +} diff --git a/src/components/forms/FormLabel.tsx b/src/components/forms/FormLabel.tsx new file mode 100644 index 0000000..525b22c --- /dev/null +++ b/src/components/forms/FormLabel.tsx @@ -0,0 +1,17 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import * as Types from "../../types" +import {BaseElement} from "../BaseElement" +import mergeClassNames from "classnames" + + +export interface FormLabelProps extends Types.BluelibHTMLProps {} + + +export function FormLabel({...props}: FormLabelProps): JSX.Element { + props.bluelibClassNames = mergeClassNames(props.bluelibClassNames, "form-label") + + return ( + + ) +} diff --git a/src/components/forms/FormMultiselect.stories.jsx b/src/components/forms/FormMultiselect.stories.jsx new file mode 100644 index 0000000..8d3dec9 --- /dev/null +++ b/src/components/forms/FormMultiselect.stories.jsx @@ -0,0 +1,35 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import * as Decorators from "../../utils/Decorators" +import { FormMultiselect as FormMultiselectComponent } from "./FormMultiselect" +import { Option } from "../inputs/Option" + + +export default { + component: FormMultiselectComponent, + title: "Forms/Form Multiselect", + decorators: [Decorators.Form, Decorators.Box, Decorators.Bluelib], + argTypes: { + onChange: {action: "Change"}, + onSimpleChange: {action: "SimpleChange"}, + }, +} + + +export const FormMultiselect = props => ( + + +) +FormMultiselect.args = { + label: "Favourite colors", +} diff --git a/src/components/forms/FormMultiselect.tsx b/src/components/forms/FormMultiselect.tsx new file mode 100644 index 0000000..1d08c0f --- /dev/null +++ b/src/components/forms/FormMultiselect.tsx @@ -0,0 +1,33 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import * as Types from "../../types" +import {BaseElement} from "../BaseElement" +import mergeClassNames from "classnames" +import {FormPair, FormPairProps} from "./FormPair"; +import {FormLabel, FormLabelProps} from "./FormLabel"; +import {Multiselect, MultiselectProps} from "../inputs/Multiselect"; + + +export interface FormMultiselectProps extends MultiselectProps { + label: string, + + validity?: Types.Validity, + + pairProps?: FormPairProps, + labelProps?: FormLabelProps, +} + + +export function FormMultiselect({label, validity, pairProps, labelProps, ...props}: FormMultiselectProps): JSX.Element { + return ( + {label}} + input={} + validity={validity} + {...pairProps} + /> + ) +} + + +FormMultiselect.Option = Multiselect.Option \ No newline at end of file diff --git a/src/components/forms/FormPair.tsx b/src/components/forms/FormPair.tsx new file mode 100644 index 0000000..a2ffa5c --- /dev/null +++ b/src/components/forms/FormPair.tsx @@ -0,0 +1,60 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import * as UUID from "uuid" +import * as Types from "../../types" +import {BaseElement} from "../BaseElement" +import mergeClassNames from "classnames" +import {FormLabel} from "./FormLabel"; +import {FormGroup} from "./FormGroup"; + + +export interface FormPairProps extends Types.BluelibProps { + label: JSX.Element, + input: JSX.Element, + // Validity is in the form pair so it can be propagated to both the label and the group + validity?: Types.Validity, + id?: string, +} + + +export function FormPair({id, label, input, validity, bluelibClassNames, customColor, disabled}: FormPairProps): JSX.Element { + if(!id) { + id = UUID.v4() + } + + let validityClass + // If the input is valid + if(validity === true) { + validityClass = "color-lime" + } + // If the input is invalid + else if(validity === false) { + validityClass = "color-red" + } + // If the input has no validity + else if(validity === null) { + validityClass = "" + } + // If no validity has been passed + else { + validityClass = "" + } + + label = React.cloneElement(label, { + htmlFor: id, + bluelibClassNames: mergeClassNames(bluelibClassNames, validityClass), + customColor: customColor, + }) + + input = React.cloneElement(input, { + id: id, + bluelibClassNames: mergeClassNames(bluelibClassNames, validityClass), + customColor: customColor, + disabled: disabled, + }) + + return <> + {label} + {input} + +} diff --git a/src/components/forms/FormRadioGroup.stories.jsx b/src/components/forms/FormRadioGroup.stories.jsx new file mode 100644 index 0000000..67ef021 --- /dev/null +++ b/src/components/forms/FormRadioGroup.stories.jsx @@ -0,0 +1,26 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import * as Decorators from "../../utils/Decorators" +import { FormRadioGroup as FormRadioGroupComponent } from "./FormRadioGroup" + + +export default { + component: FormRadioGroupComponent, + title: "Forms/Form Radio Group", + decorators: [Decorators.Form, Decorators.Box, Decorators.Bluelib], + argTypes: { + onChange: {action: "Change"}, + onSimpleChange: {action: "SimpleChange"}, + }, +} + + +export const FormRadioGroup = props => ( + +) +FormRadioGroup.args = { + label: "Size", + options: ["XS", "S", "M", "L", "XL"], + row: false, + disabled: false, +} diff --git a/src/components/forms/FormRadioGroup.tsx b/src/components/forms/FormRadioGroup.tsx new file mode 100644 index 0000000..f44129e --- /dev/null +++ b/src/components/forms/FormRadioGroup.tsx @@ -0,0 +1,75 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import * as Types from "../../types" +import * as UUID from "uuid" +import mergeClassNames from "classnames" +import {FormPair, FormPairProps} from "./FormPair"; +import {FormLabel, FormLabelProps} from "./FormLabel"; +import {FormGroup, FormGroupProps} from "./FormGroup"; +import {Radio} from "../inputs/Radio"; +import {LabelledRadio, LabelledRadioProps} from "../inputs/LabelledRadio"; + + + +export interface FormRadioGroupProps extends Types.BluelibProps { + name?: string, + label: string, + options: string[], + + row?: boolean, + + onChange?: (event: React.ChangeEvent) => void, + onSimpleChange?: (value: string) => void, + value?: string, + + validity?: Types.Validity, + + pairProps?: FormPairProps, + labelProps?: FormLabelProps, + groupProps?: FormGroupProps, + radioProps?: LabelledRadioProps, +} + + +export function FormRadioGroup({name, label, options, row, onChange, onSimpleChange, value, validity, pairProps, labelProps, groupProps, radioProps, disabled, bluelibClassNames, customColor}: FormRadioGroupProps): JSX.Element { + if(!name) { + name = UUID.v4() + } + + const radios = options.map(option => ( + + )) + + const onChangeWrapped = React.useCallback( + event => { + if(onChange) onChange(event) + if(onSimpleChange) onSimpleChange(event.target.value) + }, + [onChange, onSimpleChange] + ) + + const group = ( + + {radios} + + ) + + return ( + {label}} + input={group} + validity={validity} + bluelibClassNames={bluelibClassNames} + customColor={customColor} + {...pairProps} + /> + ) +} diff --git a/src/components/forms/FormRow.stories.jsx b/src/components/forms/FormRow.stories.jsx new file mode 100644 index 0000000..71e36ac --- /dev/null +++ b/src/components/forms/FormRow.stories.jsx @@ -0,0 +1,32 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import * as Decorators from "../../utils/Decorators" +import { FormRow as FormRowComponent } from "./FormRow" +import { Parenthesis } from "../panels/Parenthesis" +import { Button } from "../inputs/Button" +import { Separator } from "../separators/Separator" + + +export default { + component: FormRowComponent, + title: "Forms/Form Row", + decorators: [Decorators.Form, Decorators.Box, Decorators.Bluelib], +} + + +export const WithText = props => ( + + By logging in, you accept our Terms of Service. + +) +WithText.args = {} + + +export const WithButtons = props => ( + + + + + +) +WithButtons.args = {} diff --git a/src/components/forms/FormRow.tsx b/src/components/forms/FormRow.tsx new file mode 100644 index 0000000..8724a13 --- /dev/null +++ b/src/components/forms/FormRow.tsx @@ -0,0 +1,17 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import * as Types from "../../types" +import {BaseElement} from "../BaseElement" +import mergeClassNames from "classnames" + + +export interface FormRowProps extends Types.BluelibHTMLProps {} + + +export function FormRow({...props}: FormRowProps): JSX.Element { + props.bluelibClassNames = mergeClassNames(props.bluelibClassNames, "form-row") + + return ( + + ) +} diff --git a/src/components/forms/FormSelect.stories.jsx b/src/components/forms/FormSelect.stories.jsx new file mode 100644 index 0000000..1684f2a --- /dev/null +++ b/src/components/forms/FormSelect.stories.jsx @@ -0,0 +1,28 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import * as Decorators from "../../utils/Decorators" +import { FormSelect as FormSelectComponent } from "./FormSelect" +import { Option } from "../inputs/Option" + + +export default { + component: FormSelectComponent, + title: "Forms/Form Select", + decorators: [Decorators.Form, Decorators.Box, Decorators.Bluelib], + argTypes: { + onChange: {action: "Change"}, + onSimpleChange: {action: "SimpleChange"}, + }, +} + + +export const FormSelect = props => ( + + +) +FormSelect.args = { + label: "Ready check", +} diff --git a/src/components/forms/FormSelect.tsx b/src/components/forms/FormSelect.tsx new file mode 100644 index 0000000..06d47c0 --- /dev/null +++ b/src/components/forms/FormSelect.tsx @@ -0,0 +1,32 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import * as Types from "../../types" +import {BaseElement} from "../BaseElement" +import mergeClassNames from "classnames" +import {FormPair, FormPairProps} from "./FormPair"; +import {FormLabel, FormLabelProps} from "./FormLabel"; +import {Select, SelectProps} from "../inputs/Select"; + + +export interface FormSelectProps extends SelectProps { + label: string, + + validity?: Types.Validity, + + pairProps?: FormPairProps, + labelProps?: FormLabelProps, +} + + +export function FormSelect({label, validity, pairProps, labelProps, ...props}: FormSelectProps): JSX.Element { + return ( + {label}} + input={ -