-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
22 changed files
with
682 additions
and
108 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
59 changes: 59 additions & 0 deletions
59
...common-ui/lib/text-field-with-multiplication-button/TextFieldWithMultiplicationButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import React, { ChangeEvent, InputHTMLAttributes, useRef } from "react"; | ||
import { TextField, TextFieldProps } from "../formik-connected/TextField"; | ||
|
||
/** | ||
* Shows buttons for degree, minute and second entry. | ||
*/ | ||
export function TextFieldWithMultiplicationButton(props: TextFieldProps) { | ||
return ( | ||
<TextField | ||
{...props} | ||
customInput={inputProps => ( | ||
<InputWithMultiplicationButton {...inputProps} /> | ||
)} | ||
/> | ||
); | ||
} | ||
|
||
export function InputWithMultiplicationButton({ | ||
...inputProps | ||
}: InputHTMLAttributes<any>) { | ||
const inputRef = useRef<HTMLInputElement>(null); | ||
|
||
function insertSymbol(symbol: string) { | ||
const input = inputRef.current; | ||
if (input) { | ||
const text = String(inputProps.value ?? ""); | ||
const cursor = input.selectionStart ?? text.length; | ||
|
||
const newValue = `${text.slice(0, cursor)}${symbol}${text.slice(cursor)}`; | ||
|
||
inputProps.onChange?.({ | ||
target: { value: newValue } | ||
} as ChangeEvent<HTMLInputElement>); | ||
|
||
setImmediate(() => { | ||
input.focus(); | ||
input.selectionStart = cursor + 1; | ||
}); | ||
} | ||
} | ||
|
||
const symbolToAdd = "×"; | ||
|
||
return ( | ||
<div className="input-group"> | ||
<input type="text" {...inputProps} ref={inputRef} /> | ||
<button | ||
key={symbolToAdd} | ||
tabIndex={-1} | ||
className={"btn btn-info multiplication-button"} | ||
type="button" | ||
style={{ width: "3rem" }} | ||
onClick={() => insertSymbol(symbolToAdd)} | ||
> | ||
{symbolToAdd} | ||
</button> | ||
</div> | ||
); | ||
} |
17 changes: 17 additions & 0 deletions
17
...ext-field-with-multiplication-button/__tests__/TextFieldWithMultiplicationButton.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { DinaForm } from "../../formik-connected/DinaForm"; | ||
import { mountWithAppContext } from "../../test-util/mock-app-context"; | ||
import { TextFieldWithMultiplicationButton } from "../TextFieldWithMultiplicationButton"; | ||
|
||
describe("TextFieldWithMultiplicationButton component", () => { | ||
it("appends the symbol at the end.", async () => { | ||
const wrapper = mountWithAppContext( | ||
<DinaForm initialValues={{}}> | ||
<TextFieldWithMultiplicationButton name="myField" /> | ||
</DinaForm> | ||
); | ||
wrapper.find("input").simulate("change", { target: { value: "species1" } }); | ||
wrapper.find("button.multiplication-button").at(0).simulate("click"); | ||
wrapper.update(); | ||
expect(wrapper.find("input").prop("value")).toEqual("species1×"); | ||
}); | ||
}); |
175 changes: 175 additions & 0 deletions
175
packages/dina-ui/components/collection/DeterminationField.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
import { | ||
AutoSuggestTextField, | ||
DateField, | ||
FieldSet, | ||
TextField, | ||
TextFieldWithMultiplicationButton, | ||
useDinaFormContext, | ||
FormikButton | ||
} from "common-ui"; | ||
import { DinaMessage, useDinaIntl } from "../../intl/dina-ui-intl"; | ||
import { FieldArray } from "formik"; | ||
import { Determination } from "packages/dina-ui/types/collection-api/resources/Determination"; | ||
import { Tab, TabList, TabPanel, Tabs } from "react-tabs"; | ||
import { MaterialSample } from "packages/dina-ui/types/collection-api"; | ||
import { useState } from "react"; | ||
import { clamp } from "lodash"; | ||
export interface DeterminationFieldProps { | ||
className?: string; | ||
namePrefix?: string; | ||
} | ||
|
||
export const DETERMINATION_FIELDS = [ | ||
"verbatimScientificName", | ||
"verbatimAgent", | ||
"verbatimDate", | ||
"typeStatus", | ||
"typeStatusEvidence", | ||
"determiner", | ||
"determinedOn", | ||
"qualifier", | ||
"scientificNameSource", | ||
"scientificName" | ||
] as const; | ||
|
||
export function DeterminationField({ | ||
className, | ||
namePrefix = "" | ||
}: DeterminationFieldProps) { | ||
const [activeTabIdx, setActiveTabIdx] = useState(0); | ||
const { readOnly, isTemplate } = useDinaFormContext(); | ||
const determinationsPath = "determination"; | ||
return ( | ||
<FieldSet | ||
className={className} | ||
id="determination-section" | ||
legend={<DinaMessage id="determination" />} | ||
> | ||
<FieldArray name="determination"> | ||
{({ form, push, remove }) => { | ||
const determinations = | ||
(form.values.determination as Determination[]) ?? []; | ||
function addDetermination() { | ||
push({}); | ||
setActiveTabIdx(determinations.length); | ||
} | ||
|
||
function removeDetermination(index: number) { | ||
remove(index); | ||
setActiveTabIdx(current => | ||
clamp(current, 0, determinations.length - 2) | ||
); | ||
} | ||
|
||
const determinationInternal = (index, commonRoot) => ( | ||
<TabPanel key={index}> | ||
<div className="row"> | ||
<div className="col-md-6"> | ||
<TextFieldWithMultiplicationButton | ||
name={`${namePrefix}${commonRoot}verbatimScientificName`} | ||
customName="verbatimScientificName" | ||
className="col-sm-6 verbatimScientificName" | ||
/> | ||
<AutoSuggestTextField<MaterialSample> | ||
name={`${namePrefix}${commonRoot}verbatimAgent`} | ||
customName="verbatimAgent" | ||
className="col-sm-6" | ||
query={() => ({ | ||
path: "collection-api/material-sample" | ||
})} | ||
suggestion={sample => | ||
(sample.determination?.map( | ||
det => det?.verbatimAgent | ||
) as any) ?? [] | ||
} | ||
/> | ||
<DateField | ||
name={`${namePrefix}${commonRoot}verbatimDate`} | ||
customName="verbatimDate" | ||
className="col-sm-6" | ||
/> | ||
<TextField | ||
name={`${namePrefix}${commonRoot}verbatimRemarks`} | ||
customName="vebatimRemarks" | ||
multiLines={true} | ||
/> | ||
</div> | ||
<div className="col-md-6"> | ||
<TextField | ||
name={`${namePrefix}${commonRoot}typeStatus`} | ||
customName="typeStatus" | ||
multiLines={true} | ||
/> | ||
<TextField | ||
name={`${namePrefix}${commonRoot}typeStatusEvidence`} | ||
customName="typeStatusEvidence" | ||
multiLines={true} | ||
/> | ||
<TextField | ||
name={`${namePrefix}${commonRoot}qualifier`} | ||
customName="qualifier" | ||
multiLines={true} | ||
/> | ||
</div> | ||
{!readOnly && !isTemplate && ( | ||
<div className="list-inline mb-3"> | ||
<FormikButton | ||
className="list-inline-item btn btn-primary add-assertion-button" | ||
onClick={addDetermination} | ||
> | ||
<DinaMessage id="addAnotherDetermination" /> | ||
</FormikButton> | ||
<FormikButton | ||
className="list-inline-item btn btn-dark" | ||
onClick={() => removeDetermination(index)} | ||
> | ||
<DinaMessage id="removeDeterminationLabel" /> | ||
</FormikButton> | ||
</div> | ||
)} | ||
</div> | ||
</TabPanel> | ||
); | ||
// Always shows the panel without tabs when it is a template | ||
return ( | ||
<div className="determination-section"> | ||
<Tabs selectedIndex={activeTabIdx} onSelect={setActiveTabIdx}> | ||
{isTemplate ? null : ( | ||
// Only show the tabs when there is more than 1 assertion: | ||
<TabList | ||
className={`react-tabs__tab-list ${ | ||
determinations.length === 1 ? "d-none" : "" | ||
}`} | ||
> | ||
{determinations.map((_, index) => ( | ||
<Tab key={index}> | ||
<span className="m-3">{index + 1}</span> | ||
</Tab> | ||
))} | ||
</TabList> | ||
)} | ||
{isTemplate | ||
? determinationInternal(0, `${determinationsPath}[${0}].`) | ||
: determinations.length | ||
? determinations.map((_, index) => { | ||
const determinationPath = `${determinationsPath}[${index}]`; | ||
const commonRoot = determinationPath + "."; | ||
return determinationInternal(index, commonRoot); | ||
}) | ||
: null} | ||
</Tabs> | ||
{!readOnly && !isTemplate && !determinations?.length && ( | ||
<FormikButton | ||
className="list-inline-item btn btn-primary add-assertion-button" | ||
onClick={addDetermination} | ||
> | ||
<DinaMessage id="addDetermination" /> | ||
</FormikButton> | ||
)} | ||
</div> | ||
); | ||
}} | ||
</FieldArray> | ||
</FieldSet> | ||
); | ||
} |
Oops, something went wrong.