Skip to content

Commit

Permalink
Merge pull request #1215 from Chia-Network/feat/freeform-select
Browse files Browse the repository at this point in the history
feat: add freeform select
  • Loading branch information
MichaelTaylor3D authored Apr 24, 2024
2 parents 92edc30 + 67b9d49 commit f29c360
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 36 deletions.
1 change: 1 addition & 0 deletions src/renderer/components/blocks/forms/CoBenifetForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const CoBenefitForm = forwardRef<CoBenefitFormRef, CoBenefitFormProps>(
name={`${name}[${index}].cobenefit`}
label="Co-Benefit"
type="picklist"
freeform={true}
options={picklistOptions?.coBenefits}
readonly={readonly}
initialValue={coBenefit.cobenefit}
Expand Down
1 change: 1 addition & 0 deletions src/renderer/components/blocks/forms/IssuanceForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ const IssuanceForm = forwardRef<IssuanceFormRef, IssuanceFormProps>(
name={`${name}[${index}].verificationBody`}
label="Verification Body"
type="picklist"
freeform={true}
options={picklistOptions?.verificationBody}
readonly={readonly}
initialValue={issuance.verificationBody || ''}
Expand Down
14 changes: 11 additions & 3 deletions src/renderer/components/blocks/forms/ProjectForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,16 @@ const ProjectForm: React.FC<ProjectFormProps> = forwardRef<ProjectFormRef, Proje
<Field
name="sector"
label="Sector"
type="text"
type="picklist"
freeform={true}
options={picklistOptions?.projectSector}
readonly={readonly}
initialValue={data?.sector || ''}
/>
<Field
name="projectType"
label="Project Type"
freeform={true}
type="picklist"
options={picklistOptions?.projectType}
readonly={readonly}
Expand Down Expand Up @@ -196,14 +199,18 @@ const ProjectForm: React.FC<ProjectFormProps> = forwardRef<ProjectFormRef, Proje
<Field
name="currentRegistry"
label="Current Registry"
type="text"
type="picklist"
freeform={true}
options={picklistOptions?.registries}
readonly={readonly}
initialValue={data?.currentRegistry || ''}
/>
<Field
name="registryOfOrigin"
label="Registry Of Origin"
type="text"
type="picklist"
freeform={true}
options={picklistOptions?.registries}
readonly={readonly}
initialValue={data?.registryOfOrigin || ''}
/>
Expand All @@ -230,6 +237,7 @@ const ProjectForm: React.FC<ProjectFormProps> = forwardRef<ProjectFormRef, Proje
name="methodology"
label="Methodology"
type="picklist"
freeform={true}
options={picklistOptions?.methodology}
readonly={readonly}
initialValue={data?.methodology || ''}
Expand Down
6 changes: 4 additions & 2 deletions src/renderer/components/form/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface FieldProps {
label?: string;
type: 'text' | 'textarea' | 'picklist' | 'checkbox' | 'radio' | 'tag' | 'date' | 'link' | 'number';
options?: SelectOption[];
freeform?: boolean;
readonly?: boolean;
initialValue?: any;
disabled?: boolean;
Expand Down Expand Up @@ -38,7 +39,7 @@ const renderOption = (options, initialValue) => {
}
};

const Field: React.FC<FieldProps> = ({ name, label, type, options, readonly, initialValue, disabled = false }) => {
const Field: React.FC<FieldProps> = ({ name, label, type, options, readonly, initialValue, disabled = false, freeform = false }) => {
const { errors, setFieldValue }: FormikValues = useFormikContext();

const isError: boolean = !!get(errors, name);
Expand Down Expand Up @@ -108,8 +109,9 @@ const Field: React.FC<FieldProps> = ({ name, label, type, options, readonly, ini
id={name}
name={name}
disabled={disabled}
freeform={freeform}
initialValue={initialValue}
onChange={(e) => setFieldValue(name, e.target.value)}
onChange={(value) => setFieldValue(name, value)}
options={options}
/>
);
Expand Down
131 changes: 100 additions & 31 deletions src/renderer/components/proxy/Select.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import { Select as FlowbiteSelect } from 'flowbite-react';
import { PiCaretDown } from "react-icons/pi";

export type SelectOption = {
label: string;
Expand All @@ -11,53 +12,121 @@ interface SelectProps {
initialValue?: string | number;
id?: string;
name: string;
onChange?: (event: React.ChangeEvent<HTMLSelectElement>) => void;
onChange?: (value: string | number) => void;
required?: boolean;
disabled?: boolean;
freeform?: boolean; // Allows typing and filtering
}

const Select: React.FC<SelectProps> = ({ options, initialValue, id, name, onChange, required = false, disabled = false }) => {
// Helper function to determine the display text for each option
const getOptionLabel = (option: SelectOption): string => {
if (typeof option === 'object') {
return option.label;
} else {
return option.toString();
const Select: React.FC<SelectProps> = ({
options = [],
initialValue,
id,
name,
onChange,
required = false,
disabled = false,
freeform = false
}) => {
const [inputValue, setInputValue] = useState<string | number | undefined>(initialValue);
const [filteredOptions, setFilteredOptions] = useState<SelectOption[]>(options);
const [dropdownVisible, setDropdownVisible] = useState(false);

useEffect(() => {
setFilteredOptions(options);
}, [options]);

const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
setInputValue(value);
if (freeform) {
const lowerValue = value.toLowerCase();
setFilteredOptions(
options.filter(option => {
const optionValue = typeof option === 'object' ? option.label.toLowerCase() : option.toString().toLowerCase();
return optionValue.includes(lowerValue);
})
);
}
if (onChange) {
onChange(value);
}
};

const toggleDropdown = () => {
setDropdownVisible(!dropdownVisible);
};

// Helper function to determine the value for each option
const getOptionValue = (option: SelectOption): string | number => {
if (typeof option === 'object') {
return option.value;
} else {
return option;
const clearInput = () => {
setInputValue('');
setFilteredOptions(options);
setDropdownVisible(false);
if (onChange) {
onChange('');
}
};

if (freeform) {
return (
<div className="relative w-full">
<div style={{height: 40}} className="flex items-center border border-gray-300 rounded-lg">
<input
type="text"
id={id}
name={name}
value={inputValue}
onChange={handleInputChange}
onFocus={() => setDropdownVisible(true)}
onBlur={() => setTimeout(() => setDropdownVisible(false), 200)}
disabled={disabled}
className="border-none form-input block w-full px-3 py-1.5 text-base font-normal text-gray-700 bg-white bg-clip-padding rounded-l-lg transition ease-in-out focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none"
/>
{inputValue && (
<button onClick={clearInput} className="p-2 hover:bg-gray-200 focus:outline-none">
&#x2715; {/* This is a simple "X" icon */}
</button>
)}
<button onClick={toggleDropdown} className="p-2 hover:bg-gray-200 focus:outline-none">
<PiCaretDown />
</button>
</div>
{dropdownVisible && (
<ul className="absolute top-full left-0 w-full z-50 bg-white border border-gray-300 rounded shadow-lg max-h-60 overflow-auto">
{filteredOptions.map((option, index) => (
<li key={index}
onClick={() => {
setInputValue(typeof option === 'object' ? option.value : option);
setFilteredOptions(options); // reset filter on selection
setDropdownVisible(false);
if (onChange) {
onChange(typeof option === 'object' ? option.value : option);
}
}}
className="cursor-pointer px-4 py-2 hover:bg-gray-100">
{typeof option === 'object' ? option.label : option}
</li>
))}
</ul>
)}
</div>
);
}

return (
<div className="w-64">
<div className="w-full">
<FlowbiteSelect
id={id}
name={name}
defaultValue={initialValue?.toString()}
onChange={onChange}
value={inputValue?.toString()}
onChange={(e) => setInputValue(e.target.value)}
disabled={disabled}
required={required}
>
<option value="" disabled={required} selected={!initialValue}>
No Selection
</option>
{options && options.length > 0 ? (
options.map((option, index) => (
<option key={index} value={getOptionValue(option)}>
{getOptionLabel(option)}
</option>
))
) : (
<option disabled value="">
No Options Available
{options.map((option, index) => (
<option key={index} value={typeof option === 'object' ? option.value : option}>
{typeof option === 'object' ? option.label : option}
</option>
)}
))}
</FlowbiteSelect>
</div>
);
Expand Down

0 comments on commit f29c360

Please sign in to comment.