From fdeceda518493de704287e4a91a4f8a7aab39a8c Mon Sep 17 00:00:00 2001 From: Paul Stone Date: Tue, 10 Sep 2024 13:59:08 -0600 Subject: [PATCH 01/11] chore: Exports HelperText component (#1066) We want to export this component so we can use this outside of form fields. --- src/components/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/index.ts b/src/components/index.ts index ce8dbbfec..d8bddc465 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -21,6 +21,7 @@ export * from "./CssReset"; export * from "./DnDGrid"; export * from "./Filters"; export * from "./Grid"; +export * from "./HelperText"; export { HbLoadingSpinner, HbSpinnerProvider, HB_QUIPS_FLAVOR, HB_QUIPS_MISSION } from "./HbLoadingSpinner"; export * from "./Icon"; export * from "./IconButton"; From 9c1bc1a3e01002aa3124034d00cf252d69249a2c Mon Sep 17 00:00:00 2001 From: Paul Stone Date: Wed, 11 Sep 2024 11:00:15 -0600 Subject: [PATCH 02/11] fix: force a release for a previous pull request (#1067) Previous PR (https://github.com/homebound-team/beam/pull/1066) was pushed as a chore by mistake when a release was needed. Pushed this empty commit to force a release. From 8f6bfe71a925e092d8fa73cf3f872f778d64a50a Mon Sep 17 00:00:00 2001 From: Homebound Bot Date: Wed, 11 Sep 2024 17:03:40 +0000 Subject: [PATCH 03/11] chore(release): 2.365.1 [skip ci] ## [2.365.1](https://github.com/homebound-team/beam/compare/v2.365.0...v2.365.1) (2024-09-11) ### Bug Fixes * force a release for a previous pull request ([#1067](https://github.com/homebound-team/beam/issues/1067)) ([9c1bc1a](https://github.com/homebound-team/beam/commit/9c1bc1a3e01002aa3124034d00cf252d69249a2c)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 742d919ab..a4e320640 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@homebound/beam", - "version": "2.365.0", + "version": "2.365.1", "author": "Homebound", "license": "MIT", "main": "dist/index.js", From 09a569095f383f0d507d82ee5149affa01c35c9b Mon Sep 17 00:00:00 2001 From: Jonnathan Charpentier <92122357+JonnCharpentier@users.noreply.github.com> Date: Mon, 16 Sep 2024 12:04:14 -0600 Subject: [PATCH 04/11] feat: Initial work for add-new-option on select field (#1068) Co-authored-by: Brandon Dow Co-authored-by: JonnCh --- src/inputs/MultiSelectField.tsx | 3 +- src/inputs/SelectField.test.tsx | 21 ++++++++++++- src/inputs/SelectField.tsx | 2 +- src/inputs/internal/ComboBoxBase.tsx | 45 +++++++++++++++++++++++----- 4 files changed, 61 insertions(+), 10 deletions(-) diff --git a/src/inputs/MultiSelectField.tsx b/src/inputs/MultiSelectField.tsx index 9ef88f3c3..9f10344e4 100644 --- a/src/inputs/MultiSelectField.tsx +++ b/src/inputs/MultiSelectField.tsx @@ -3,7 +3,8 @@ import { Value } from "src/inputs"; import { ComboBoxBase, ComboBoxBaseProps } from "src/inputs/internal/ComboBoxBase"; import { HasIdAndName, Optional } from "src/types"; -export interface MultiSelectFieldProps extends Exclude, "unsetLabel"> { +export interface MultiSelectFieldProps + extends Exclude, "unsetLabel" | "addNew"> { /** Renders `opt` in the dropdown menu, defaults to the `getOptionLabel` prop. */ getOptionMenuLabel?: (opt: O) => string | ReactNode; getOptionValue: (opt: O) => V; diff --git a/src/inputs/SelectField.test.tsx b/src/inputs/SelectField.test.tsx index 5429f194f..b369a5477 100644 --- a/src/inputs/SelectField.test.tsx +++ b/src/inputs/SelectField.test.tsx @@ -1,4 +1,4 @@ -import { clickAndWait } from "@homebound/rtl-utils"; +import { clickAndWait, type } from "@homebound/rtl-utils"; import { fireEvent } from "@testing-library/react"; import { useState } from "react"; import { SelectField, SelectFieldProps, Value } from "src/inputs"; @@ -472,6 +472,25 @@ describe("SelectFieldTest", () => { expect(onSelect.mock.calls[2][0]).toBe(undefined); }); + it("allows to add a new option", async () => { + // Given a SelectField + const onAddNew = jest.fn(); + const r = await render( + o.name} + getOptionValue={(o) => o.id} + onAddNew={onAddNew} + />, + ); + // When we select Add New option + select(r.age, "Add New"); + // Then onAddNew was called + expect(onAddNew).toHaveBeenCalledTimes(1); + }); + // Used to validate the `unset` option can be applied to non-`HasIdAndName` options type HasLabelAndValue = { label: string; diff --git a/src/inputs/SelectField.tsx b/src/inputs/SelectField.tsx index 44a6d1807..2c540857b 100644 --- a/src/inputs/SelectField.tsx +++ b/src/inputs/SelectField.tsx @@ -1,7 +1,7 @@ import { useMemo } from "react"; import { Value } from "src/inputs"; import { ComboBoxBase, ComboBoxBaseProps, unsetOption } from "src/inputs/internal/ComboBoxBase"; -import { HasIdAndName, HasIdIsh, HasNameIsh, Optional } from "src/types"; +import { HasIdIsh, HasNameIsh, Optional } from "src/types"; import { defaultOptionLabel, defaultOptionValue } from "src/utils/options"; export interface SelectFieldProps diff --git a/src/inputs/internal/ComboBoxBase.tsx b/src/inputs/internal/ComboBoxBase.tsx index e2650be30..396cd79f3 100644 --- a/src/inputs/internal/ComboBoxBase.tsx +++ b/src/inputs/internal/ComboBoxBase.tsx @@ -17,7 +17,7 @@ import equal from "fast-deep-equal"; /** Base props for either `SelectField` or `MultiSelectField`. */ export interface ComboBoxBaseProps extends BeamFocusableProps, PresentationFieldProps { /** Renders `opt` in the dropdown menu, defaults to the `getOptionLabel` prop. `isUnsetOpt` is only defined for single SelectField */ - getOptionMenuLabel?: (opt: O, isUnsetOpt?: boolean) => string | ReactNode; + getOptionMenuLabel?: (opt: O, isUnsetOpt?: boolean, isAddNewOption?: boolean) => string | ReactNode; getOptionValue: (opt: O) => V; getOptionLabel: (opt: O) => string; /** The current value; it can be `undefined`, even if `V` cannot be. */ @@ -65,6 +65,8 @@ export interface ComboBoxBaseProps extends BeamFocusableProp multiline?: boolean; /* Callback for user searches */ onSearch?: (search: string) => void; + /* Only supported on single Select fields */ + onAddNew?: (v: string) => void; } /** @@ -95,26 +97,39 @@ export function ComboBoxBase(props: ComboBoxBaseProps) getOptionMenuLabel: propOptionMenuLabel, fullWidth = fieldProps?.fullWidth ?? false, onSearch, + onAddNew, ...otherProps } = props; const labelStyle = otherProps.labelStyle ?? fieldProps?.labelStyle ?? "above"; // Memoize the callback functions and handle the `unset` option if provided. const getOptionLabel = useCallback( - (o: O) => (unsetLabel && o === unsetOption ? unsetLabel : propOptionLabel(o)), + (o: O) => + unsetLabel && o === unsetOption + ? unsetLabel + : onAddNew && o === addNewOption + ? addNewOption.name + : propOptionLabel(o), // propOptionLabel is basically always a lambda, so don't dep on it // eslint-disable-next-line react-hooks/exhaustive-deps [unsetLabel], ); const getOptionValue = useCallback( - (o: O) => (unsetLabel && o === unsetOption ? (undefined as V) : propOptionValue(o)), + (o: O) => + unsetLabel && o === unsetOption + ? (undefined as V) + : onAddNew && o === addNewOption + ? (addNewOption.id as V) + : propOptionValue(o), // propOptionValue is basically always a lambda, so don't dep on it // eslint-disable-next-line react-hooks/exhaustive-deps [unsetLabel], ); const getOptionMenuLabel = useCallback( (o: O) => - propOptionMenuLabel ? propOptionMenuLabel(o, Boolean(unsetLabel) && o === unsetOption) : getOptionLabel(o), + propOptionMenuLabel + ? propOptionMenuLabel(o, Boolean(unsetLabel) && o === unsetOption, Boolean(onAddNew) && o === addNewOption) + : getOptionLabel(o), // propOptionMenuLabel is basically always a lambda, so don't dep on it // eslint-disable-next-line react-hooks/exhaustive-deps [unsetLabel, getOptionLabel], @@ -122,11 +137,13 @@ export function ComboBoxBase(props: ComboBoxBaseProps) // Call `initializeOptions` to prepend the `unset` option if the `unsetLabel` was provided. const options = useMemo( - () => initializeOptions(propOptions, getOptionValue, unsetLabel), + () => initializeOptions(propOptions, getOptionValue, unsetLabel, !!onAddNew), // If the caller is using { current, load, options }, memoize on only `current` and `options` values. // ...and don't bother on memoizing on getOptionValue b/c it's basically always a lambda // eslint-disable-next-line react-hooks/exhaustive-deps - Array.isArray(propOptions) ? [propOptions, unsetLabel] : [propOptions.current, propOptions.options, unsetLabel], + Array.isArray(propOptions) + ? [propOptions, unsetLabel, onAddNew] + : [propOptions.current, propOptions.options, unsetLabel, onAddNew], ); const values = useMemo(() => propValues ?? [], [propValues]); @@ -160,7 +177,9 @@ export function ComboBoxBase(props: ComboBoxBaseProps) const { searchValue } = fieldState; const filteredOptions = useMemo(() => { - return !searchValue ? options : options.filter((o) => contains(getOptionLabel(o), searchValue)); + return !searchValue + ? options + : options.filter((o) => contains(getOptionLabel(o), searchValue) || o === addNewOption); }, [options, searchValue, getOptionLabel, contains]); /** Resets field's input value and filtered options list for cases where the user exits the field without making changes (on Escape, or onBlur) */ @@ -189,6 +208,13 @@ export function ComboBoxBase(props: ComboBoxBaseProps) const selectedKeys = [...keys.values()]; const selectedOptions = options.filter((o) => selectedKeys.includes(valueToKey(getOptionValue(o)))); + + if (!multiselect && selectedOptions[0] === addNewOption && onAddNew) { + onAddNew(fieldState.inputValue); + state.close(); + return; + } + selectionChanged && onSelect(selectedKeys.map(keyToValue) as V[], selectedOptions); if (!multiselect) { @@ -442,6 +468,7 @@ export function initializeOptions( optionsOrLoad: OptionsOrLoad, getOptionValue: (opt: O) => V, unsetLabel: string | undefined, + addNew: boolean, ): O[] { const opts: O[] = []; if (unsetLabel) { @@ -466,11 +493,15 @@ export function initializeOptions( }); } } + if (addNew) { + opts.push(addNewOption as unknown as O); + } return opts; } /** A marker option to automatically add an "Unset" option to the start of options. */ export const unsetOption = {}; +export const addNewOption = { id: "new", name: "Add New" }; export function disabledOptionToKeyedTuple( disabledOption: Value | { value: Value; reason: string }, From 6c4d22b6ab88737bca80464badb9d1d98a26cddc Mon Sep 17 00:00:00 2001 From: Homebound Bot Date: Mon, 16 Sep 2024 18:07:23 +0000 Subject: [PATCH 05/11] chore(release): 2.366.0 [skip ci] ## [2.366.0](https://github.com/homebound-team/beam/compare/v2.365.1...v2.366.0) (2024-09-16) ### Features * Initial work for add-new-option on select field ([#1068](https://github.com/homebound-team/beam/issues/1068)) ([09a5690](https://github.com/homebound-team/beam/commit/09a569095f383f0d507d82ee5149affa01c35c9b)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a4e320640..00b883cea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@homebound/beam", - "version": "2.365.1", + "version": "2.366.0", "author": "Homebound", "license": "MIT", "main": "dist/index.js", From f763b7a9bd5c69ab14b4a54d6faabfde0d4c99ae Mon Sep 17 00:00:00 2001 From: Jonnathan Charpentier <92122357+JonnCharpentier@users.noreply.github.com> Date: Wed, 18 Sep 2024 12:37:58 -0600 Subject: [PATCH 06/11] fix: Updating trix-initialize when readOnly changes (#1069) Co-authored-by: JonnCh --- src/inputs/RichTextField.tsx | 99 +++++++++++++++++------------------- 1 file changed, 47 insertions(+), 52 deletions(-) diff --git a/src/inputs/RichTextField.tsx b/src/inputs/RichTextField.tsx index 185a9be24..9a7fb456e 100644 --- a/src/inputs/RichTextField.tsx +++ b/src/inputs/RichTextField.tsx @@ -69,65 +69,60 @@ export function RichTextField(props: RichTextFieldProps) { onFocusRef.current = onFocus; // Generate a unique id to be used for matching `trix-initialize` event for this instance. - const id = useMemo( - () => { - if (readOnly) return; - - const id = `trix-editor-${trixId}`; - trixId++; - - function onEditorInit(e: Event) { - const targetEl = e.target as HTMLElement; - if (targetEl.id === id) { - editorElement.current = targetEl; - const editor = (editorElement.current as any).editor; - setEditor(editor); - if (mergeTags !== undefined) { - attachTributeJs(mergeTags, editorElement.current!); - } + const id = useMemo(() => { + if (readOnly) return; + + const id = `trix-editor-${trixId}`; + trixId++; + + function onEditorInit(e: Event) { + const targetEl = e.target as HTMLElement; + if (targetEl.id === id) { + editorElement.current = targetEl; + const editor = (editorElement.current as any).editor; + setEditor(editor); + if (mergeTags !== undefined) { + attachTributeJs(mergeTags, editorElement.current!); + } - currentHtml.current = value; - editor.loadHTML(value || ""); - // Remove listener once we've initialized - window.removeEventListener("trix-initialize", onEditorInit); - - function trixChange(e: ChangeEvent) { - const { textContent, innerHTML } = e.target; - const onChange = onChangeRef.current; - // If the user only types whitespace, treat that as undefined - if ((textContent || "").trim() === "") { - currentHtml.current = undefined; - onChange && onChange(undefined, undefined, []); - } else { - currentHtml.current = innerHTML; - const mentions = extractIdsFromMentions(mergeTags || [], textContent || ""); - onChange && onChange(innerHTML, textContent || undefined, mentions); - } + currentHtml.current = value; + editor.loadHTML(value || ""); + // Remove listener once we've initialized + window.removeEventListener("trix-initialize", onEditorInit); + + function trixChange(e: ChangeEvent) { + const { textContent, innerHTML } = e.target; + const onChange = onChangeRef.current; + // If the user only types whitespace, treat that as undefined + if ((textContent || "").trim() === "") { + currentHtml.current = undefined; + onChange && onChange(undefined, undefined, []); + } else { + currentHtml.current = innerHTML; + const mentions = extractIdsFromMentions(mergeTags || [], textContent || ""); + onChange && onChange(innerHTML, textContent || undefined, mentions); } + } - const trixBlur = () => maybeCall(onBlurRef.current); - const trixFocus = () => maybeCall(onFocusRef.current); + const trixBlur = () => maybeCall(onBlurRef.current); + const trixFocus = () => maybeCall(onFocusRef.current); - // We don't want to allow file attachment for now. In addition to hiding the button, also disable drag-and-drop - // https://github.com/basecamp/trix#storing-attached-files - const preventDefault = (e: any) => e.preventDefault(); - window.addEventListener("trix-file-accept", preventDefault); + // We don't want to allow file attachment for now. In addition to hiding the button, also disable drag-and-drop + // https://github.com/basecamp/trix#storing-attached-files + const preventDefault = (e: any) => e.preventDefault(); + window.addEventListener("trix-file-accept", preventDefault); - editorElement.current.addEventListener("trix-change", trixChange as any); - editorElement.current.addEventListener("trix-blur", trixBlur); - editorElement.current.addEventListener("trix-focus", trixFocus); - } + editorElement.current.addEventListener("trix-change", trixChange as any); + editorElement.current.addEventListener("trix-blur", trixBlur); + editorElement.current.addEventListener("trix-focus", trixFocus); } + } - // Attaching listener to the `window` to we're listening prior to render. - // The web component's `trix-initialize` event may fire before a `useEffect` hook in the component is executed, making it difficult ot attach the event listener locally. - window.addEventListener("trix-initialize", onEditorInit); - return id; - }, - // TODO: validate this eslint-disable. It was automatically ignored as part of https://app.shortcut.com/homebound-team/story/40033/enable-react-hooks-exhaustive-deps-for-react-projects - // eslint-disable-next-line react-hooks/exhaustive-deps - [], - ); + // Attaching listener to the `window` to we're listening prior to render. + // The web component's `trix-initialize` event may fire before a `useEffect` hook in the component is executed, making it difficult ot attach the event listener locally. + window.addEventListener("trix-initialize", onEditorInit); + return id; + }, [readOnly]); useEffect(() => { // If our value prop changes (without the change coming from us), reload it From 646362bf3f4be7dd13d059a4c8c13ae9f4904358 Mon Sep 17 00:00:00 2001 From: Homebound Bot Date: Wed, 18 Sep 2024 18:40:50 +0000 Subject: [PATCH 07/11] chore(release): 2.366.1 [skip ci] ## [2.366.1](https://github.com/homebound-team/beam/compare/v2.366.0...v2.366.1) (2024-09-18) ### Bug Fixes * Updating trix-initialize when readOnly changes ([#1069](https://github.com/homebound-team/beam/issues/1069)) ([f763b7a](https://github.com/homebound-team/beam/commit/f763b7a9bd5c69ab14b4a54d6faabfde0d4c99ae)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 00b883cea..23f9128b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@homebound/beam", - "version": "2.366.0", + "version": "2.366.1", "author": "Homebound", "license": "MIT", "main": "dist/index.js", From 2636b4e8df0c2e616781ebf12ad5fad330fc21ce Mon Sep 17 00:00:00 2001 From: zovington <96074138+zovington@users.noreply.github.com> Date: Mon, 23 Sep 2024 07:23:44 -0600 Subject: [PATCH 08/11] feat: mills currency (#1071) https://app.shortcut.com/homebound-team/story/58640/inmill-beam-support Add support for Mills which is 3 decimal places --- src/forms/BoundNumberField.test.tsx | 9 +++++++++ src/forms/BoundNumberField.tsx | 4 ++-- src/forms/formStateDomain.ts | 1 + src/inputs/NumberField.stories.tsx | 2 ++ src/inputs/NumberField.test.tsx | 18 ++++++++++++++++++ src/inputs/NumberField.tsx | 8 ++++++-- src/inputs/RichTextField.tsx | 1 + 7 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/forms/BoundNumberField.test.tsx b/src/forms/BoundNumberField.test.tsx index 88fc361f5..38ddc5f45 100644 --- a/src/forms/BoundNumberField.test.tsx +++ b/src/forms/BoundNumberField.test.tsx @@ -57,6 +57,14 @@ describe("BoundNumberField", () => { expect(r.royalties).toHaveValue("$1.00"); }); + it("drops the 'in mills' suffix from labels", async () => { + const author = createObjectState(formConfig, { royaltiesInMills: 1_00 }); + const r = await render(); + expect(r.royalties_label).toHaveTextContent("Royalties"); + expect(r.royalties_label).not.toHaveTextContent("Cents"); + expect(r.royalties).toHaveValue("$0.100"); + }); + it("retains 0 value", async () => { const author = createObjectState(formConfig, { royaltiesInCents: 1_00 }); const r = await render(); @@ -117,4 +125,5 @@ describe("BoundNumberField", () => { const formConfig: ObjectConfig = { heightInInches: { type: "value", rules: [required] }, royaltiesInCents: { type: "value" }, + royaltiesInMills: { type: "value" }, }; diff --git a/src/forms/BoundNumberField.tsx b/src/forms/BoundNumberField.tsx index 267be6e4c..55654de91 100644 --- a/src/forms/BoundNumberField.tsx +++ b/src/forms/BoundNumberField.tsx @@ -18,8 +18,8 @@ export function BoundNumberField(props: BoundNumberFieldProps) { field, readOnly, onChange = (value) => field.set(value), - label = defaultLabel(field.key.replace(/InCents$/, "")), - type = field.key.endsWith("InCents") ? "cents" : undefined, + label = defaultLabel(field.key.replace(/(InCents|InMills)$/, "")), + type = field.key.endsWith("InCents") ? "cents" : field.key.endsWith("InMills") ? "mills" : undefined, onFocus, onBlur, onEnter, diff --git a/src/forms/formStateDomain.ts b/src/forms/formStateDomain.ts index 00bea4279..e74856bc4 100644 --- a/src/forms/formStateDomain.ts +++ b/src/forms/formStateDomain.ts @@ -26,6 +26,7 @@ export interface AuthorInput { birthday?: Date | null; heightInInches?: number | null; royaltiesInCents?: number | null; + royaltiesInMills?: number | null; books?: BookInput[] | null; address?: AuthorAddress | null; favoriteSport?: string | null; diff --git a/src/inputs/NumberField.stories.tsx b/src/inputs/NumberField.stories.tsx index 07237f211..4a165bdda 100644 --- a/src/inputs/NumberField.stories.tsx +++ b/src/inputs/NumberField.stories.tsx @@ -45,6 +45,7 @@ export function NumberFieldStyles() {

Unit Types

+ @@ -80,6 +81,7 @@ export function NumberFieldReadOnly() { {}} readOnly={true} /> +
diff --git a/src/inputs/NumberField.test.tsx b/src/inputs/NumberField.test.tsx index 7d66a475a..c0481cbab 100644 --- a/src/inputs/NumberField.test.tsx +++ b/src/inputs/NumberField.test.tsx @@ -60,6 +60,14 @@ describe("NumberFieldTest", () => { expect(onChange).toBeCalledTimes(2); // change and blur }); + it("can set mills as dollars", async () => { + const r = await render(); + expect(r.cost).toHaveValue("$1.200"); + type(r.cost, "14"); + expect(r.cost).toHaveValue("$14.000"); + expect(lastSet).toEqual(14000); + }); + it("can set cents as dollars", async () => { const r = await render(); expect(r.cost).toHaveValue("$12.00"); @@ -92,6 +100,14 @@ describe("NumberFieldTest", () => { expect(onChange).toBeCalledTimes(2); // change and blur }); + it("can set mills as mills", async () => { + const r = await render(); + expect(r.cost).toHaveValue("$12.000"); + type(r.cost, ".145"); + expect(r.cost).toHaveValue("$0.145"); + expect(lastSet).toEqual(145); + }); + it("can set cents as cents", async () => { const r = await render(); expect(r.cost).toHaveValue("$12.00"); @@ -153,6 +169,7 @@ describe("NumberFieldTest", () => { const r = await render( <> + @@ -160,6 +177,7 @@ describe("NumberFieldTest", () => { , ); expect(r.days).toHaveValue("+123 days"); + expect(r.mills).toHaveValue("+$0.456"); expect(r.cents).toHaveValue("+$4.56"); expect(r.basisPoints).toHaveValue("+7.89%"); expect(r.percent).toHaveValue("+123%"); diff --git a/src/inputs/NumberField.tsx b/src/inputs/NumberField.tsx index efe8fc962..a5d4e8d7e 100644 --- a/src/inputs/NumberField.tsx +++ b/src/inputs/NumberField.tsx @@ -8,7 +8,7 @@ import { Css, Xss } from "src/Css"; import { maybeCall } from "src/utils"; import { TextFieldBase } from "./TextFieldBase"; -export type NumberFieldType = "cents" | "dollars" | "percent" | "basisPoints" | "days"; +export type NumberFieldType = "cents" | "dollars" | "percent" | "basisPoints" | "days" | "mills"; // exported for testing purposes export interface NumberFieldProps extends Pick { @@ -85,7 +85,9 @@ export function NumberField(props: NumberFieldProps) { const isDisabled = !!disabled; const isReadOnly = !!readOnly; - const factor = type === "percent" || type === "cents" ? 100 : type === "basisPoints" ? 10_000 : 1; + const factor = + type === "percent" ? 100 : type === "cents" ? 100 : type === "mills" ? 1_000 : type === "basisPoints" ? 10_000 : 1; + const signDisplay = displayDirection ? "always" : "auto"; const defaultFormatOptions: Intl.NumberFormatOptions = useMemo( () => ({ @@ -111,6 +113,8 @@ export function NumberField(props: NumberFieldProps) { ? { style: "percent", minimumFractionDigits: 2 } : type === "cents" ? { style: "currency", currency: "USD", minimumFractionDigits: 2 } + : type === "mills" + ? { style: "currency", currency: "USD", minimumFractionDigits: 3 } : type === "dollars" ? { style: "currency", currency: "USD", minimumFractionDigits: numFractionDigits ?? 2 } : type === "days" diff --git a/src/inputs/RichTextField.tsx b/src/inputs/RichTextField.tsx index 9a7fb456e..0823a33ad 100644 --- a/src/inputs/RichTextField.tsx +++ b/src/inputs/RichTextField.tsx @@ -122,6 +122,7 @@ export function RichTextField(props: RichTextFieldProps) { // The web component's `trix-initialize` event may fire before a `useEffect` hook in the component is executed, making it difficult ot attach the event listener locally. window.addEventListener("trix-initialize", onEditorInit); return id; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [readOnly]); useEffect(() => { From 66af1b7a13e37187074e116de75674bd26ebeb2c Mon Sep 17 00:00:00 2001 From: Homebound Bot Date: Mon, 23 Sep 2024 13:28:24 +0000 Subject: [PATCH 09/11] chore(release): 2.367.0 [skip ci] ## [2.367.0](https://github.com/homebound-team/beam/compare/v2.366.1...v2.367.0) (2024-09-23) ### Features * mills currency ([#1071](https://github.com/homebound-team/beam/issues/1071)) ([2636b4e](https://github.com/homebound-team/beam/commit/2636b4e8df0c2e616781ebf12ad5fad330fc21ce)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 23f9128b9..494b02152 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@homebound/beam", - "version": "2.366.1", + "version": "2.367.0", "author": "Homebound", "license": "MIT", "main": "dist/index.js", From 6cc40094a6a7d91971cb01bafae650b38791ac82 Mon Sep 17 00:00:00 2001 From: Jonnathan Charpentier <92122357+JonnCharpentier@users.noreply.github.com> Date: Mon, 23 Sep 2024 11:38:17 -0600 Subject: [PATCH 10/11] feat: Add select on focus to text field (#1072) Co-authored-by: JonnCh --- src/components/SuperDrawer/components/SuperDrawerHeader.tsx | 4 ++-- src/inputs/TextField.tsx | 2 ++ src/inputs/TextFieldBase.tsx | 5 ++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/SuperDrawer/components/SuperDrawerHeader.tsx b/src/components/SuperDrawer/components/SuperDrawerHeader.tsx index de38c7b69..ee03fea3e 100644 --- a/src/components/SuperDrawer/components/SuperDrawerHeader.tsx +++ b/src/components/SuperDrawer/components/SuperDrawerHeader.tsx @@ -12,7 +12,7 @@ interface SuperDrawerHeaderProps { } interface SuperDrawerHeaderStructuredProps { - title: string; + title: string | ReactNode; left?: ReactNode; right?: ReactNode; hideControls?: boolean; @@ -32,7 +32,7 @@ export function SuperDrawerHeader(props: SuperDrawerHeaderProps | SuperDrawerHea {isStructuredProps(props) ? (
-

{props.title}

+ {typeof props.title === "string" ?

{props.title}

: props.title} {props.left}
{props.right &&
{props.right}
} diff --git a/src/inputs/TextField.tsx b/src/inputs/TextField.tsx index 159981feb..a6e51a046 100644 --- a/src/inputs/TextField.tsx +++ b/src/inputs/TextField.tsx @@ -25,6 +25,8 @@ export interface TextFieldProps extends BeamTextFieldProps { endAdornment?: ReactNode; startAdornment?: ReactNode; hideErrorMessage?: boolean; + /** Allow focusing without selecting, i.e. to let the user keep typing after we've pre-filled text + called focus, like the Add New component. */ + selectOnFocus?: boolean; } export function TextField>(props: TextFieldProps) { diff --git a/src/inputs/TextFieldBase.tsx b/src/inputs/TextFieldBase.tsx index 9b66b79d5..98092e68f 100644 --- a/src/inputs/TextFieldBase.tsx +++ b/src/inputs/TextFieldBase.tsx @@ -61,6 +61,8 @@ export interface TextFieldBaseProps // Replaces empty input field and placeholder with node // IE: Multiselect renders list of selected items in the input field unfocusedPlaceholder?: ReactNode; + /** Allow focusing without selecting, i.e. to let the user keep typing after we've pre-filled text + called focus, like the Add New component. */ + selectOnFocus?: boolean; } // Used by both TextField and TextArea @@ -97,6 +99,7 @@ export function TextFieldBase>(props: TextFieldB alwaysShowHelperText = false, fullWidth = fieldProps?.fullWidth ?? false, unfocusedPlaceholder, + selectOnFocus = true, } = props; const typeScale = fieldProps?.typeScale ?? (inputProps.readOnly && labelStyle !== "hidden" ? "smMd" : "sm"); @@ -188,7 +191,7 @@ export function TextFieldBase>(props: TextFieldB } const onFocusChained = chain((e: FocusEvent | FocusEvent) => { - e.target.select(); + if (selectOnFocus) e.target.select(); }, onFocus); // Simulate clicking `ElementType` when using an unfocused placeholder From 4f3cb49a8a6173823443cba02b1ee7ccad945463 Mon Sep 17 00:00:00 2001 From: Homebound Bot Date: Mon, 23 Sep 2024 17:41:19 +0000 Subject: [PATCH 11/11] chore(release): 2.368.0 [skip ci] ## [2.368.0](https://github.com/homebound-team/beam/compare/v2.367.0...v2.368.0) (2024-09-23) ### Features * Add select on focus to text field ([#1072](https://github.com/homebound-team/beam/issues/1072)) ([6cc4009](https://github.com/homebound-team/beam/commit/6cc40094a6a7d91971cb01bafae650b38791ac82)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 494b02152..7ebf19d76 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@homebound/beam", - "version": "2.367.0", + "version": "2.368.0", "author": "Homebound", "license": "MIT", "main": "dist/index.js",