diff --git a/apps/reactlit-examples/src/pages/hello-world-vanilla/index.tsx b/apps/reactlit-examples/src/pages/hello-world-vanilla/index.tsx index 9e009aa..9f0d928 100644 --- a/apps/reactlit-examples/src/pages/hello-world-vanilla/index.tsx +++ b/apps/reactlit-examples/src/pages/hello-world-vanilla/index.tsx @@ -6,6 +6,8 @@ export default function HelloWorldVanilla() { name: '', pickedNumbers: [], pickedColors: [], + chosenNumber: '', + chosenColor: '', }); return ( @@ -24,9 +26,14 @@ export default function HelloWorldVanilla() { const picked = view( 'pickedNumbers', Inputs.Check(['One', 'Two', 'Three'], { - className: 'border p-0.5 mr-1', + className: { + container: 'flex gap-2 items-center', + wrapper: 'flex gap-2', + item: { + input: 'border p-0.5 mr-1', + }, + }, label: 'Pick any number', - containerClassName: 'flex gap-2', }) ); display(
Picked: {picked.join(', ')}!
); @@ -40,9 +47,14 @@ export default function HelloWorldVanilla() { { label: 'White', value: '#FFFFFF' }, ], { - className: 'border p-0.5 mr-1', + className: { + container: 'flex gap-2 items-center', + wrapper: 'flex gap-2', + item: { + input: 'border p-0.5 mr-1', + }, + }, label: 'Pick any color', - containerClassName: 'flex gap-2', valueof: (item) => item.value, format: (item) => ( ), - keyof: (item) => item.label, - disabled: ['White'], + disabled: (item) => item.value === '#FFFFFF', } ) ); display(
Colors: {JSON.stringify(pickedColors)}!
); + const chosenNumber = view( + 'chosenNumber', + Inputs.Radio(['One', 'Two', 'Three'], { + className: { + container: 'flex gap-2 items-center', + wrapper: 'flex gap-2', + item: { + input: 'border p-0.5 mr-1', + }, + }, + label: 'Choose a number', + }) + ); + display(
Chosen Number: {chosenNumber}!
); + const chosenColor = view( + 'chosenColor', + Inputs.Radio( + [ + { label: 'Red', value: '#FF0000' }, + { label: 'Green', value: '#00FF00' }, + { label: 'Blue', value: '#0000FF' }, + { label: 'White', value: '#FFFFFF' }, + ], + { + className: { + container: 'flex gap-2 items-center', + wrapper: 'flex gap-2', + item: { + input: 'border p-0.5 mr-1', + }, + }, + label: 'Choose a color', + valueof: (item) => item.value, + format: (item) => ( + + {item.label} + + ), + disabled: (item) => item.value === '#FFFFFF', + } + ) + ); + display(
Chosen Color: {JSON.stringify(chosenColor)}!
); }}
); diff --git a/libs/vanilla/src/index.ts b/libs/vanilla/src/index.ts index 4668cdc..8a5d69b 100644 --- a/libs/vanilla/src/index.ts +++ b/libs/vanilla/src/index.ts @@ -1,7 +1,9 @@ import { CheckInput } from './inputs/check.input'; +import { RadioInput } from './inputs/radio.input'; import { TextInput } from './inputs/text.input'; export const Inputs = { Text: TextInput, Check: CheckInput, + Radio: RadioInput, }; diff --git a/libs/vanilla/src/inputs/check.input.tsx b/libs/vanilla/src/inputs/check.input.tsx index e5157b2..3bb376e 100644 --- a/libs/vanilla/src/inputs/check.input.tsx +++ b/libs/vanilla/src/inputs/check.input.tsx @@ -11,15 +11,23 @@ export type CheckInputProps = Omit< React.InputHTMLAttributes, HTMLInputElement >, - 'value' | 'disabled' + 'value' | 'disabled' | 'className' > & { data: T[]; label?: string | React.ReactNode; - containerClassName?: string; + className?: { + container?: string; + wrapper?: string; + label?: string; + item?: { + wrapper?: string; + input?: string; + label?: string; + }; + }; format?: (value: T) => string | React.ReactNode; valueof?: (value: T) => string; - keyof?: (value: T) => string; - disabled?: string[]; + disabled?: string[] | ((value: T) => boolean); }; export const CheckInputComponent = ({ @@ -28,43 +36,53 @@ export const CheckInputComponent = ({ setValue, onChange, data, - containerClassName, + className, format, - keyof, valueof, disabled, label, ...props }: CheckInputProps & ViewComponentProps<(string | T)[]>) => { return ( -
- {label && } -
+
+ {label && ( + + )} +
{data.map((item) => { const isChecked = !!value.find((v) => valueof ? valueof(item) === v : item === v ); - const itemKey = `${stateKey}-${keyof?.(item) ?? item.toString()}`; + const itemKey = `${stateKey}-${valueof?.(item) ?? item.toString()}`; + let disabledValue = false; + + if (typeof disabled === 'function') { + disabledValue = disabled(item); + } else if (Array.isArray(disabled)) { + disabledValue = disabled.includes( + valueof?.(item) ?? item.toString() + ); + } return ( -
+
{ const _value = valueof?.(item) ?? item; if (e.target.checked) setValue([...value, _value]); else setValue(value.filter((v) => v !== _value)); }} - disabled={ - (keyof && disabled?.includes(keyof(item))) || - disabled?.includes(valueof?.(item) ?? item.toString()) - } + disabled={disabledValue} {...props} /> -
diff --git a/libs/vanilla/src/inputs/radio.input.tsx b/libs/vanilla/src/inputs/radio.input.tsx new file mode 100644 index 0000000..06100b0 --- /dev/null +++ b/libs/vanilla/src/inputs/radio.input.tsx @@ -0,0 +1,118 @@ +import { + defineTransformView, + defineView, + ViewComponentProps, + ViewDefinition, +} from '@reactlit/core'; +import { DetailedHTMLProps } from 'react'; + +export type RadioInputProps = Omit< + DetailedHTMLProps< + React.InputHTMLAttributes, + HTMLInputElement + >, + 'value' | 'disabled' | 'className' +> & { + data: T[]; + label?: string | React.ReactNode; + className?: { + container?: string; + wrapper?: string; + label?: string; + item?: { + wrapper?: string; + input?: string; + label?: string; + }; + }; + format?: (value: T) => string | React.ReactNode; + valueof?: (value: T) => string; + disabled?: string[] | ((value: T) => boolean); +}; + +export const RadioInputComponent = ({ + value, + stateKey, + setValue, + onChange, + data, + className, + format, + valueof, + disabled, + label, + ...props +}: RadioInputProps & ViewComponentProps) => { + return ( +
+ {label && ( + + )} +
+ {data.map((item) => { + const isChecked = valueof ? valueof(item) === value : item === value; + const itemKey = `${stateKey}-${valueof?.(item) ?? item.toString()}`; + let disabledValue = false; + + if (typeof disabled === 'function') { + disabledValue = disabled(item); + } else if (Array.isArray(disabled)) { + disabledValue = disabled.includes( + valueof?.(item) ?? item.toString() + ); + } + + return ( +
+ { + const _value = valueof?.(item) ?? item; + if (e.target.checked) setValue(_value); + }} + disabled={disabledValue} + {...props} + /> + +
+ ); + })} +
+
+ ); +}; + +export type RadioInputDefinition = P extends { valueof: (v: T) => string } + ? ViewDefinition + : ViewDefinition; + +export const RadioInput = >( + data: P['data'], + { valueof, ...props }: Omit +): RadioInputDefinition => { + if (valueof) { + return defineTransformView( + (viewProps) => ( + + ), + ({ value }) => data.find((d) => valueof(d) === value) + ) as RadioInputDefinition; + } else { + return defineView((viewProps) => ( + + )) as RadioInputDefinition; + } +};