diff --git a/package.json b/package.json index 1141c29..5aca810 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "@leafygreen-ui/inline-definition": "^9.0.5", "@leafygreen-ui/leafygreen-provider": "^5.0.2", "@leafygreen-ui/palette": "^5.0.0", + "@leafygreen-ui/select": "^16.2.0", "@leafygreen-ui/tokens": "^3.2.1", "@leafygreen-ui/tooltip": "^14.2.1", "@leafygreen-ui/typography": "^22.1.0", diff --git a/src/components/canvas/canvas.tsx b/src/components/canvas/canvas.tsx index 2c72598..4a7b41a 100644 --- a/src/components/canvas/canvas.tsx +++ b/src/components/canvas/canvas.tsx @@ -59,10 +59,12 @@ export const Canvas = ({ edges: externalEdges, onConnect, id, + fieldTypes, onAddFieldToNodeClick, onNodeExpandToggle, onAddFieldToObjectFieldClick, onFieldNameChange, + onFieldTypeChange, onFieldClick, onNodeContextMenu, onNodeDrag, @@ -153,6 +155,8 @@ export const Canvas = ({ onNodeExpandToggle={onNodeExpandToggle} onAddFieldToObjectFieldClick={onAddFieldToObjectFieldClick} onFieldNameChange={onFieldNameChange} + onFieldTypeChange={onFieldTypeChange} + fieldTypes={fieldTypes} > { + const [isEditing, setIsEditing] = useState(false); + const fieldContentRef = useRef(null); + + const { onChangeFieldName, onChangeFieldType, fieldTypes } = useEditableDiagramInteractions(); + const handleNameChange = useCallback( + (newName: string) => onChangeFieldName?.(nodeId, Array.isArray(id) ? id : [id], newName), + [onChangeFieldName, id, nodeId], + ); + const handleTypeChange = useCallback( + (newType: string[]) => onChangeFieldType?.(nodeId, Array.isArray(id) ? id : [id], newType), + [onChangeFieldType, id, nodeId], + ); + + const handleDoubleClick = useCallback(() => { + setIsEditing(true); + }, []); + + useEffect(() => { + // When clicking outside of the field content while editing, stop editing. + const container = fieldContentRef.current; + const listener = (event: Event) => { + if (event.composedPath().includes(container!)) { + return; + } + setIsEditing(false); + }; + + if (container && isEditable) { + document.addEventListener('click', listener); + } else { + document.removeEventListener('click', listener); + } + return () => { + document.removeEventListener('click', listener); + }; + }, [isEditable]); + + useEffect(() => { + if (!isEditable) { + setIsEditing(false); + } + }, [isEditable]); + + const isNameEditable = isEditing && isEditable && !!onChangeFieldName; + const isTypeEditable = isEditing && isEditable && !!onChangeFieldType && (fieldTypes ?? []).length > 0; + + return ( + + + + setIsEditing(false)} + /> + + + + ); +}; diff --git a/src/components/field/field-name-content.tsx b/src/components/field/field-name-content.tsx index 05d7094..2531c74 100644 --- a/src/components/field/field-name-content.tsx +++ b/src/components/field/field-name-content.tsx @@ -18,17 +18,17 @@ const InlineInput = styled.input` font-size: inherit; font-family: inherit; font-style: inherit; + width: 100%; `; interface FieldNameProps { name: string; - isEditable?: boolean; - onChange?: (newName: string) => void; - onBlur?: () => void; + isEditing: boolean; + onChange: (newName: string) => void; + onCancelEditing: () => void; } -export const FieldNameContent = ({ name, isEditable, onChange }: FieldNameProps) => { - const [isEditing, setIsEditing] = useState(false); +export const FieldNameContent = ({ name, isEditing, onChange, onCancelEditing }: FieldNameProps) => { const [value, setValue] = useState(name); const textInputRef = useRef(null); @@ -37,8 +37,7 @@ export const FieldNameContent = ({ name, isEditable, onChange }: FieldNameProps) }, [name]); const handleSubmit = useCallback(() => { - setIsEditing(false); - onChange?.(value); + onChange(value); }, [value, onChange]); const handleKeyboardEvent = useCallback( @@ -46,29 +45,16 @@ export const FieldNameContent = ({ name, isEditable, onChange }: FieldNameProps) if (e.key === 'Enter') handleSubmit(); if (e.key === 'Escape') { setValue(name); - setIsEditing(false); + onCancelEditing(); } }, - [handleSubmit, name], + [handleSubmit, onCancelEditing, name], ); - const handleNameDoubleClick = useCallback(() => { - setIsEditing(true); - }, []); - const handleChange = useCallback((e: React.ChangeEvent) => { setValue(e.target.value); }, []); - useEffect(() => { - if (isEditing) { - setTimeout(() => { - textInputRef.current?.focus(); - textInputRef.current?.select(); - }); - } - }, [isEditing]); - return isEditing ? ( ) : ( - {value} + {value} ); }; diff --git a/src/components/field/field-type-content.tsx b/src/components/field/field-type-content.tsx index f860491..e481824 100644 --- a/src/components/field/field-type-content.tsx +++ b/src/components/field/field-type-content.tsx @@ -31,10 +31,12 @@ export const FieldTypeContent = ({ type, nodeId, id, + isAddFieldToObjectDisabled, }: { id: string | string[]; nodeId: string; type?: string | string[]; + isAddFieldToObjectDisabled?: boolean; }) => { const { onClickAddFieldToObjectField: _onClickAddFieldToObjectField } = useEditableDiagramInteractions(); @@ -53,8 +55,8 @@ export const FieldTypeContent = ({ if (type === 'object') { return ( - {'{}'} - {onClickAddFieldToObject && ( + {'{}'} + {onClickAddFieldToObject && !isAddFieldToObjectDisabled && ( {'[]'}; } if (Array.isArray(type)) { @@ -78,7 +80,7 @@ export const FieldTypeContent = ({ } if (type.length === 1) { - return <>{type}; + return {type}; } const typesString = type.join(', '); @@ -93,5 +95,5 @@ export const FieldTypeContent = ({ ); } - return <>{type}; + return {type}; }; diff --git a/src/components/field/field-type.tsx b/src/components/field/field-type.tsx new file mode 100644 index 0000000..650c3c9 --- /dev/null +++ b/src/components/field/field-type.tsx @@ -0,0 +1,137 @@ +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; +import { spacing, color } from '@leafygreen-ui/tokens'; +import { Select, Option } from '@leafygreen-ui/select'; +import Icon from '@leafygreen-ui/icon'; +import { useEffect, useRef, useState } from 'react'; + +import { ellipsisTruncation } from '@/styles/styles'; +import { FieldTypeContent } from '@/components/field/field-type-content'; +import { FieldId } from '@/types'; +import { useEditableDiagramInteractions } from '@/hooks/use-editable-diagram-interactions'; + +const FieldTypeWrapper = styled.div<{ color: string }>` + color: ${props => props.color}; + font-weight: normal; + padding-left:${spacing[100]}px; + padding-right ${spacing[50]}px; + flex: 0 0 ${spacing[200] * 10}px; + display: flex; + justify-content: flex-end; + align-items: center; +`; + +const FieldContentWrapper = styled.div` + max-width: ${spacing[200] * 10}px; + ${ellipsisTruncation} +`; + +const CaretIconWrapper = styled.div` + display: flex; +`; + +const StyledSelect = styled(Select)` + visibility: hidden; + height: 0; + width: 0; + & > button { + height: 0; + width: 0; + border: none; + box-shadow: none; + } +`; + +export function FieldType({ + id, + type, + nodeId, + isEditing, + isDisabled, + onChange, +}: { + id: FieldId; + nodeId: string; + type: string | string[] | undefined; + isEditing: boolean; + isDisabled: boolean; + onChange: (newType: string[]) => void; +}) { + const internalTheme = useTheme(); + const { theme } = useDarkMode(); + const { fieldTypes } = useEditableDiagramInteractions(); + const [isSelectOpen, setIsSelectOpen] = useState(false); + const fieldTypeRef = useRef(null); + + useEffect(() => { + if (!isEditing) { + setIsSelectOpen(false); + } + }, [isEditing]); + + const getSecondaryTextColor = () => { + if (isDisabled) { + return internalTheme.node.disabledColor; + } + return color[theme].text.secondary.default; + }; + + return ( + setIsSelectOpen(!isSelectOpen), + } + : undefined)} + color={getSecondaryTextColor()} + > + {/** + * Rendering hidden select first so that whenever popover shows it, its relative + * to the field type position. LG Select does not provide a way to set the + * position of the popover using refs. + */} + {isEditing && ( + { + if (val) { + // Currently its a single select, so we are returning it as an array. + // That way once we have multi-select support, we don't need to change + // the API and it should work seemlessly for clients. + // Trigger onChange only if the value is different + if (type !== val) { + onChange([val]); + } + setIsSelectOpen(false); + } + }} + // As its not multi-select, we can just use the first value. Once LG-5657 + // is implemented, we can use ComboBox component for multi-select support + value={Array.isArray(type) ? type[0] : type || ''} + allowDeselect={false} + dropdownWidthBasis="option" + tabIndex={0} + > + {fieldTypes!.map(fieldType => ( + + ))} + + )} + + + + {isEditing && ( + + + + )} + + ); +} diff --git a/src/components/field/field.test.tsx b/src/components/field/field.test.tsx index 334106c..7b190a8 100644 --- a/src/components/field/field.test.tsx +++ b/src/components/field/field.test.tsx @@ -16,15 +16,21 @@ const Field = (props: React.ComponentProps) => ( const FieldWithEditableInteractions = ({ onAddFieldToObjectFieldClick, onFieldNameChange, + onFieldTypeChange, + fieldTypes, ...fieldProps }: React.ComponentProps & { onAddFieldToObjectFieldClick?: () => void; onFieldNameChange?: (newName: string) => void; + onFieldTypeChange?: (nodeId: string, fieldPath: string[], newTypes: string[]) => void; + fieldTypes?: string[]; }) => { return ( @@ -98,6 +104,7 @@ describe('field', () => { {...DEFAULT_PROPS} id={fieldId} editable={true} + selected={true} onFieldNameChange={onFieldNameChangeMock} />, ); @@ -153,6 +160,123 @@ describe('field', () => { await rerender(); expect(screen.getByText(newName)).toBeInTheDocument(); }); + + describe('Field type editing', () => { + it('Should not allow editing when field is not selected', async () => { + render( + , + ); + const fieldWrapper = screen.getByTestId('field-content-ordersId'); + expect(fieldWrapper).toBeInTheDocument(); + await userEvent.dblClick(fieldWrapper); + expect(screen.queryByText('Select field type')).not.toBeInTheDocument(); + }); + it('Should not allow editing when field is disabled', async () => { + render( + , + ); + const fieldWrapper = screen.getByTestId('field-content-ordersId'); + expect(fieldWrapper).toBeInTheDocument(); + await userEvent.dblClick(fieldWrapper); + expect(screen.queryByText('Select field type')).not.toBeInTheDocument(); + }); + it('Should not allow editing when no callback is provided', async () => { + render( + , + ); + const fieldWrapper = screen.getByTestId('field-content-ordersId'); + expect(fieldWrapper).toBeInTheDocument(); + await userEvent.dblClick(fieldWrapper); + expect(screen.queryByText('Select field type')).not.toBeInTheDocument(); + }); + it('Should not allow editing when no fieldTypes are provided', async () => { + render( + , + ); + const fieldWrapper = screen.getByTestId('field-content-ordersId'); + expect(fieldWrapper).toBeInTheDocument(); + await userEvent.dblClick(fieldWrapper); + expect(screen.queryByText('Select field type')).not.toBeInTheDocument(); + }); + it('Should allow editing', async () => { + const onFieldTypeChangeMock = vi.fn(); + render( + , + ); + const fieldWrapper = screen.getByTestId('field-content-ordersId'); + expect(fieldWrapper).toBeInTheDocument(); + await userEvent.dblClick(fieldWrapper); + + const caretWrapper = screen.getByLabelText('Select field type'); + expect(caretWrapper).toBeInTheDocument(); + await userEvent.click(caretWrapper); + + expect(onFieldTypeChangeMock).not.toHaveBeenCalled(); + const stringOption = screen.getByRole('option', { name: 'string' }); + await userEvent.click(stringOption); + expect(onFieldTypeChangeMock).toHaveBeenCalledWith( + DEFAULT_PROPS.nodeId, + Array.isArray(DEFAULT_PROPS.id) ? DEFAULT_PROPS.id : [DEFAULT_PROPS.id], + ['string'], + ); + expect(onFieldTypeChangeMock).toHaveBeenCalledTimes(1); + + // Try changing to number type + await userEvent.click(caretWrapper); + const numberOption = screen.getByRole('option', { name: 'number' }); + await userEvent.click(numberOption); + expect(onFieldTypeChangeMock).toHaveBeenCalledWith( + DEFAULT_PROPS.nodeId, + Array.isArray(DEFAULT_PROPS.id) ? DEFAULT_PROPS.id : [DEFAULT_PROPS.id], + ['number'], + ); + expect(onFieldTypeChangeMock).toHaveBeenCalledTimes(2); + }); + }); }); describe('With specific types', () => { diff --git a/src/components/field/field.tsx b/src/components/field/field.tsx index e066418..a611b40 100644 --- a/src/components/field/field.tsx +++ b/src/components/field/field.tsx @@ -1,20 +1,18 @@ import styled from '@emotion/styled'; -import { color, fontWeights, spacing as LGSpacing, spacing } from '@leafygreen-ui/tokens'; +import { color, spacing as LGSpacing, spacing } from '@leafygreen-ui/tokens'; import { palette } from '@leafygreen-ui/palette'; import Icon from '@leafygreen-ui/icon'; import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; import { useTheme } from '@emotion/react'; -import { useCallback, useMemo } from 'react'; +import { useMemo } from 'react'; -import { animatedBlueBorder, ellipsisTruncation } from '@/styles/styles'; +import { animatedBlueBorder } from '@/styles/styles'; import { DEFAULT_DEPTH_SPACING, DEFAULT_FIELD_HEIGHT } from '@/utilities/constants'; -import { FieldDepth } from '@/components/field/field-depth'; -import { FieldTypeContent } from '@/components/field/field-type-content'; import { FieldId, NodeField, NodeGlyph, NodeType } from '@/types'; import { PreviewGroupArea } from '@/utilities/get-preview-group-area'; import { useEditableDiagramInteractions } from '@/hooks/use-editable-diagram-interactions'; -import { FieldNameContent } from './field-name-content'; +import { FieldContent } from './field-content'; const FIELD_BORDER_ANIMATED_PADDING = spacing[100]; const FIELD_GLYPH_SPACING = spacing[400]; @@ -99,24 +97,6 @@ const FieldRow = styled.div` align-items: center; `; -const FieldName = styled.div` - display: flex; - flex-grow: 1; - align-items: center; - font-weight: ${fontWeights.medium}; - ${ellipsisTruncation} -`; - -const FieldType = styled.div` - color: ${props => props.color}; - flex: 0 0 ${LGSpacing[200] * 10}px; - font-weight: normal; - text-align: right; - padding-left:${LGSpacing[100]}px; - padding-right ${LGSpacing[50]}px; - ${ellipsisTruncation} -`; - const IconWrapper = styled(Icon)` padding-right: ${spacing[100]}px; flex-shrink: 0; @@ -153,7 +133,7 @@ export const Field = ({ }: Props) => { const { theme } = useDarkMode(); - const { onClickField, onChangeFieldName } = useEditableDiagramInteractions(); + const { onClickField } = useEditableDiagramInteractions(); const internalTheme = useTheme(); @@ -184,14 +164,6 @@ export const Field = ({ } }; - const getSecondaryTextColor = () => { - if (isDisabled) { - return internalTheme.node.disabledColor; - } else { - return color[theme].text.secondary.default; - } - }; - const getIconColor = (glyph: NodeGlyph) => { if (isDisabled) { return color[theme].text.disabled.default; @@ -211,25 +183,16 @@ export const Field = ({ return internalTheme.node.mongoDBAccent; }; - const handleNameChange = useCallback( - (newName: string) => onChangeFieldName?.(nodeId, Array.isArray(id) ? id : [id], newName), - [onChangeFieldName, id, nodeId], - ); - const content = ( - <> - - - - - - - - + ); /** diff --git a/src/hooks/use-editable-diagram-interactions.tsx b/src/hooks/use-editable-diagram-interactions.tsx index 061f9a3..ee73bab 100644 --- a/src/hooks/use-editable-diagram-interactions.tsx +++ b/src/hooks/use-editable-diagram-interactions.tsx @@ -6,6 +6,7 @@ import { OnNodeExpandHandler, OnAddFieldToObjectFieldClickHandler, OnFieldNameChangeHandler, + OnFieldTypeChangeHandler, } from '@/types'; interface EditableDiagramInteractionsContextType { @@ -14,26 +15,32 @@ interface EditableDiagramInteractionsContextType { onNodeExpandToggle?: OnNodeExpandHandler; onClickAddFieldToObjectField?: OnAddFieldToObjectFieldClickHandler; onChangeFieldName?: OnFieldNameChangeHandler; + onChangeFieldType?: OnFieldTypeChangeHandler; + fieldTypes?: string[]; } const EditableDiagramInteractionsContext = createContext(undefined); interface EditableDiagramInteractionsProviderProps { children: ReactNode; + fieldTypes?: string[]; onFieldClick?: OnFieldClickHandler; onAddFieldToNodeClick?: OnAddFieldToNodeClickHandler; onNodeExpandToggle?: OnNodeExpandHandler; onAddFieldToObjectFieldClick?: OnAddFieldToObjectFieldClickHandler; onFieldNameChange?: OnFieldNameChangeHandler; + onFieldTypeChange?: OnFieldTypeChangeHandler; } export const EditableDiagramInteractionsProvider: React.FC = ({ children, + fieldTypes, onFieldClick, onAddFieldToNodeClick, onNodeExpandToggle, onAddFieldToObjectFieldClick, onFieldNameChange, + onFieldTypeChange, }) => { const value: EditableDiagramInteractionsContextType = useMemo(() => { return { @@ -62,8 +69,22 @@ export const EditableDiagramInteractionsProvider: React.FC{children} diff --git a/src/mocks/decorators/diagram-editable-interactions.decorator.tsx b/src/mocks/decorators/diagram-editable-interactions.decorator.tsx index 493a826..e47e470 100644 --- a/src/mocks/decorators/diagram-editable-interactions.decorator.tsx +++ b/src/mocks/decorators/diagram-editable-interactions.decorator.tsx @@ -3,6 +3,30 @@ import { Decorator } from '@storybook/react'; import { DiagramProps, FieldId, NodeField, NodeProps } from '@/types'; +const fieldTypes = [ + 'double', + 'string', + 'object', + 'array', + 'binData', + 'undefined', + 'objectId', + 'bool', + 'date', + 'null', + 'regex', + 'dbPointer', + 'javascript', + 'symbol', + 'javascriptWithScope', + 'int', + 'timestamp', + 'long', + 'decimal', + 'minKey', + 'maxKey', +]; + function stringArrayCompare(a: string[], b: string[]): boolean { if (a.length !== b.length) return false; if (a === b) return true; @@ -20,6 +44,7 @@ const newField = (parentFieldPath?: string[]) => { name, type: 'string', selectable: true, + editable: true, depth: parentFieldPath ? parentFieldPath.length : 0, id: parentFieldPath ? [...parentFieldPath, name] : [name], }; @@ -57,6 +82,24 @@ function renameField(existingFields: NodeField[], fieldPath: string[], newName: return fields; } +function changeFieldType(existingFields: NodeField[], fieldPath: string[], newTypes: string[]) { + let currentType; + const fields = existingFields.map(field => { + if (JSON.stringify(field.id) !== JSON.stringify(fieldPath)) return field; + currentType = field.type; + return { ...field, type: Array.isArray(newTypes) && newTypes.length === 1 ? newTypes[0] : newTypes }; + }); + // If the currentType is 'object' or 'array', we should also remove all child fields. + if (currentType === 'object' || currentType === 'array') { + return fields.filter(field => { + const type = typeof field.id === 'string' ? [field.id] : field.id || []; + // Leading period helps ignore the current field (where type was changed). + return !type.join('.').startsWith(`${fieldPath.join('.')}.`); + }); + } + return fields; +} + let idAccumulator: string[]; let lastDepth = 0; // Used to build a string array id based on field depth. @@ -192,6 +235,19 @@ export const useEditableNodes = (initialNodes: NodeProps[]) => { ); }, []); + const onFieldTypeChange = useCallback((nodeId: string, fieldPath: string[], newTypes: string[]) => { + setNodes(nodes => + nodes.map(node => + node.id === nodeId + ? { + ...node, + fields: changeFieldType(node.fields, fieldPath, newTypes), + } + : node, + ), + ); + }, []); + const onNodeExpandToggle = useCallback((_evt: ReactMouseEvent, nodeId: string) => { setExpanded(state => { return { @@ -201,6 +257,20 @@ export const useEditableNodes = (initialNodes: NodeProps[]) => { }); }, []); + const onNodeDragStop = useCallback((_event: ReactMouseEvent, node: NodeProps) => { + setNodes(nodes => { + return nodes.map(n => { + if (n.id === node.id) { + return { + ...n, + position: node.position, + }; + } + return n; + }); + }); + }, []); + const _nodes = useMemo(() => { return nodes.map(node => { if (expanded[node.id]) { @@ -222,6 +292,9 @@ export const useEditableNodes = (initialNodes: NodeProps[]) => { onNodeExpandToggle, onAddFieldToObjectFieldClick, onFieldNameChange, + onFieldTypeChange, + fieldTypes, + onNodeDragStop, }; }; diff --git a/src/types/component-props.ts b/src/types/component-props.ts index 1350657..b98b150 100644 --- a/src/types/component-props.ts +++ b/src/types/component-props.ts @@ -40,6 +40,11 @@ export type OnAddFieldToObjectFieldClickHandler = (event: ReactMouseEvent, nodeI */ export type OnFieldNameChangeHandler = (nodeId: string, fieldPath: string[], newName: string) => void; +/** + * Called when a field's type is edited. + */ +export type OnFieldTypeChangeHandler = (nodeId: string, fieldPath: string[], newTypes: string[]) => void; + /** * Called when the canvas (pane) is clicked. */ @@ -204,6 +209,16 @@ export interface DiagramProps { */ onFieldNameChange?: OnFieldNameChangeHandler; + /** + * Callback when a field's type is changed. + */ + onFieldTypeChange?: OnFieldTypeChangeHandler; + + /** + * List of available field types for editing. + */ + fieldTypes?: string[]; + /** * Whether the diagram should pan when dragging elements. */ diff --git a/yarn.lock b/yarn.lock index 0ed3db9..8a73daf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1093,6 +1093,35 @@ __metadata: languageName: node linkType: hard +"@leafygreen-ui/a11y@npm:^3.0.5": + version: 3.0.5 + resolution: "@leafygreen-ui/a11y@npm:3.0.5" + dependencies: + "@leafygreen-ui/emotion": "npm:^5.0.3" + "@leafygreen-ui/hooks": "npm:^9.1.4" + "@leafygreen-ui/lib": "npm:^15.4.0" + checksum: 10c0/4ff9330cbc41b0869263ba1914f37643d894ca3d16dcac0206ce6b46f5c300ab51bfec9eb65e303c4c1f99d9e98dc880cc04e49e92072989610c15dd655f9014 + languageName: node + linkType: hard + +"@leafygreen-ui/button@npm:^25.1.2": + version: 25.1.2 + resolution: "@leafygreen-ui/button@npm:25.1.2" + dependencies: + "@leafygreen-ui/emotion": "npm:^5.1.0" + "@leafygreen-ui/lib": "npm:^15.6.2" + "@leafygreen-ui/palette": "npm:^5.0.2" + "@leafygreen-ui/polymorphic": "npm:^3.1.0" + "@leafygreen-ui/ripple": "npm:^2.0.7" + "@leafygreen-ui/tokens": "npm:^4.0.0" + "@lg-tools/test-harnesses": "npm:^0.3.4" + polished: "npm:^4.2.2" + peerDependencies: + "@leafygreen-ui/leafygreen-provider": ">=3.2.0" + checksum: 10c0/a4a994e56541cacf3f05ba13d6ad49f4512e461030d0d828a937640609d667f9247c4015c94d755bf304e44b9666f4115daff35248ffa6a730f4fea6d1bddb16 + languageName: node + linkType: hard + "@leafygreen-ui/emotion@npm:^5.0.0": version: 5.0.0 resolution: "@leafygreen-ui/emotion@npm:5.0.0" @@ -1123,6 +1152,33 @@ __metadata: languageName: node linkType: hard +"@leafygreen-ui/emotion@npm:^5.1.0": + version: 5.1.0 + resolution: "@leafygreen-ui/emotion@npm:5.1.0" + dependencies: + "@emotion/css": "npm:^11.1.3" + "@emotion/server": "npm:^11.4.0" + checksum: 10c0/4ea012d7f4e9b17158dd4ff0cc9d302421fc211ccbacaae397c71a569c68c73e5152e24d7b4b63508d104c792f44498c182260ff63e9f026a6423fe13150a6e0 + languageName: node + linkType: hard + +"@leafygreen-ui/form-field@npm:^4.0.6": + version: 4.0.6 + resolution: "@leafygreen-ui/form-field@npm:4.0.6" + dependencies: + "@leafygreen-ui/emotion": "npm:^5.1.0" + "@leafygreen-ui/hooks": "npm:^9.2.2" + "@leafygreen-ui/icon": "npm:^14.6.1" + "@leafygreen-ui/lib": "npm:^15.6.2" + "@leafygreen-ui/palette": "npm:^5.0.2" + "@leafygreen-ui/tokens": "npm:^4.0.0" + "@leafygreen-ui/typography": "npm:^22.2.0" + peerDependencies: + "@leafygreen-ui/leafygreen-provider": ">=3.2.0" + checksum: 10c0/f6561634b9ec7510cc7364e2d12e3283bef7e645a089350bc7e6b9a851e561aa96ee5fe2b9c77caff443bdf1d5ff45f51931553a51abfa6576a45752a078726f + languageName: node + linkType: hard + "@leafygreen-ui/hooks@npm:^9.1.1": version: 9.1.1 resolution: "@leafygreen-ui/hooks@npm:9.1.1" @@ -1156,6 +1212,17 @@ __metadata: languageName: node linkType: hard +"@leafygreen-ui/hooks@npm:^9.2.2, @leafygreen-ui/hooks@npm:^9.3.0": + version: 9.3.0 + resolution: "@leafygreen-ui/hooks@npm:9.3.0" + dependencies: + "@leafygreen-ui/lib": "npm:^15.6.2" + "@leafygreen-ui/tokens": "npm:^4.0.0" + lodash: "npm:^4.17.21" + checksum: 10c0/02f3f51aa0535b58347c3f0f22375bf3bcf3d008a8a66dcd455fad4c5e5ab1affc9b4588fd32bea274df215661d2c6aa10989e5b1273ecae11a7f67b3d970465 + languageName: node + linkType: hard + "@leafygreen-ui/icon@npm:^14.1.0, @leafygreen-ui/icon@npm:^14.3.0": version: 14.3.0 resolution: "@leafygreen-ui/icon@npm:14.3.0" @@ -1186,6 +1253,17 @@ __metadata: languageName: node linkType: hard +"@leafygreen-ui/icon@npm:^14.6.1, @leafygreen-ui/icon@npm:^14.7.0": + version: 14.7.0 + resolution: "@leafygreen-ui/icon@npm:14.7.0" + dependencies: + "@leafygreen-ui/emotion": "npm:^5.1.0" + "@leafygreen-ui/hooks": "npm:^9.3.0" + lodash: "npm:^4.17.21" + checksum: 10c0/117459888ea8bf2cee2ef9b07fb87ebaa95eb7198e694314039891fe5019e81dd6b27a2a0e27599d72e86921a0c7f5ce907f144f8e74ac9d3117bd0fd617d7fc + languageName: node + linkType: hard + "@leafygreen-ui/inline-definition@npm:^9.0.5": version: 9.0.5 resolution: "@leafygreen-ui/inline-definition@npm:9.0.5" @@ -1201,6 +1279,23 @@ __metadata: languageName: node linkType: hard +"@leafygreen-ui/input-option@npm:^4.1.2": + version: 4.1.2 + resolution: "@leafygreen-ui/input-option@npm:4.1.2" + dependencies: + "@leafygreen-ui/a11y": "npm:^3.0.5" + "@leafygreen-ui/emotion": "npm:^5.1.0" + "@leafygreen-ui/lib": "npm:^15.6.2" + "@leafygreen-ui/palette": "npm:^5.0.2" + "@leafygreen-ui/polymorphic": "npm:^3.1.0" + "@leafygreen-ui/tokens": "npm:^4.0.0" + "@leafygreen-ui/typography": "npm:^22.2.0" + peerDependencies: + "@leafygreen-ui/leafygreen-provider": ">=3.2.0" + checksum: 10c0/bf84bc4076e1c473087771a15b6b2108fe5605e327c7a4ab9a93f512ccf614bd9ecc5bcd4911ed20c655e1110ca349fb64e2c78bab37b7dd3db05b270efb6eb7 + languageName: node + linkType: hard + "@leafygreen-ui/leafygreen-provider@npm:^5.0.2": version: 5.0.2 resolution: "@leafygreen-ui/leafygreen-provider@npm:5.0.2" @@ -1317,6 +1412,25 @@ __metadata: languageName: node linkType: hard +"@leafygreen-ui/popover@npm:^14.3.0": + version: 14.3.0 + resolution: "@leafygreen-ui/popover@npm:14.3.0" + dependencies: + "@floating-ui/react": "npm:^0.26.28" + "@leafygreen-ui/emotion": "npm:^5.1.0" + "@leafygreen-ui/hooks": "npm:^9.2.2" + "@leafygreen-ui/lib": "npm:^15.6.2" + "@leafygreen-ui/portal": "npm:^7.1.0" + "@leafygreen-ui/tokens": "npm:^4.0.0" + "@types/react-transition-group": "npm:^4.4.5" + lodash: "npm:^4.17.21" + react-transition-group: "npm:^4.4.5" + peerDependencies: + "@leafygreen-ui/leafygreen-provider": ">=3.2.0" + checksum: 10c0/9671ff62dd0a22a9cad4b16455a1c4fd2db91a0b760ce0cd44e8aed1687f6f14bf6ee4d5295809cf1776f571bd92ddd2eec435342a141c24a1ac800e99a2ca5e + languageName: node + linkType: hard + "@leafygreen-ui/portal@npm:^7.0.4": version: 7.0.4 resolution: "@leafygreen-ui/portal@npm:7.0.4" @@ -1341,6 +1455,41 @@ __metadata: languageName: node linkType: hard +"@leafygreen-ui/ripple@npm:^2.0.7": + version: 2.0.7 + resolution: "@leafygreen-ui/ripple@npm:2.0.7" + dependencies: + "@leafygreen-ui/tokens": "npm:^4.0.0" + checksum: 10c0/1af18bbc287b39fdfd1f8e5e57a63a9fa4b4962b139933384a0c9238d7536db2f481e2a8924d5451b28224ff4c341c6d4dff8888f131b6a083e1698d65a34fb6 + languageName: node + linkType: hard + +"@leafygreen-ui/select@npm:^16.2.0": + version: 16.3.0 + resolution: "@leafygreen-ui/select@npm:16.3.0" + dependencies: + "@leafygreen-ui/button": "npm:^25.1.2" + "@leafygreen-ui/emotion": "npm:^5.1.0" + "@leafygreen-ui/form-field": "npm:^4.0.6" + "@leafygreen-ui/hooks": "npm:^9.2.2" + "@leafygreen-ui/icon": "npm:^14.6.1" + "@leafygreen-ui/input-option": "npm:^4.1.2" + "@leafygreen-ui/lib": "npm:^15.6.2" + "@leafygreen-ui/palette": "npm:^5.0.2" + "@leafygreen-ui/popover": "npm:^14.3.0" + "@leafygreen-ui/tokens": "npm:^4.0.0" + "@leafygreen-ui/typography": "npm:^22.2.0" + "@lg-tools/test-harnesses": "npm:^0.3.4" + "@types/react-is": "npm:^18.0.0" + lodash: "npm:^4.17.21" + polished: "npm:^4.1.3" + react-is: "npm:^18.0.1" + peerDependencies: + "@leafygreen-ui/leafygreen-provider": ">=3.2.0" + checksum: 10c0/4e669333ba32e59da662c00fab970f80a8e2b837d896768c6f8f0ccc1ba9bce2158b29c4ab251d90d7c7e74502bc829405668b4b2a78ad4968293facbcb53838 + languageName: node + linkType: hard + "@leafygreen-ui/tokens@npm:^3.1.2, @leafygreen-ui/tokens@npm:^3.2.0, @leafygreen-ui/tokens@npm:^3.2.1": version: 3.2.1 resolution: "@leafygreen-ui/tokens@npm:3.2.1" @@ -1365,6 +1514,17 @@ __metadata: languageName: node linkType: hard +"@leafygreen-ui/tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "@leafygreen-ui/tokens@npm:4.0.0" + dependencies: + "@leafygreen-ui/emotion": "npm:^5.1.0" + "@leafygreen-ui/lib": "npm:^15.6.2" + "@leafygreen-ui/palette": "npm:^5.0.2" + checksum: 10c0/425ff51ba5c7ba5310e05866c02f102e3b8e14dd4e655337dc1efd3eb52c5bc13392265e8ef70431b79c7acd742cd892ad32b2aa2c1a77afbb11b6c0501ff0a1 + languageName: node + linkType: hard + "@leafygreen-ui/tooltip@npm:^14.1.3": version: 14.1.4 resolution: "@leafygreen-ui/tooltip@npm:14.1.4" @@ -1453,6 +1613,31 @@ __metadata: languageName: node linkType: hard +"@leafygreen-ui/typography@npm:^22.2.0": + version: 22.2.1 + resolution: "@leafygreen-ui/typography@npm:22.2.1" + dependencies: + "@leafygreen-ui/emotion": "npm:^5.1.0" + "@leafygreen-ui/icon": "npm:^14.7.0" + "@leafygreen-ui/lib": "npm:^15.6.2" + "@leafygreen-ui/palette": "npm:^5.0.2" + "@leafygreen-ui/polymorphic": "npm:^3.1.0" + "@leafygreen-ui/tokens": "npm:^4.0.0" + peerDependencies: + "@leafygreen-ui/leafygreen-provider": "*" + checksum: 10c0/eba0b033cdcb8b3d622cccc68089ea90a7b0a5c5dbff66e04b442fba924cdbc1e58f45cbf7d814012c9966f210e1c5ff9b97e367f8f898d476eaffd65433ac61 + languageName: node + linkType: hard + +"@lg-tools/test-harnesses@npm:^0.3.4": + version: 0.3.4 + resolution: "@lg-tools/test-harnesses@npm:0.3.4" + dependencies: + "@testing-library/dom": "npm:9.3.1" + checksum: 10c0/4d6ea8020a62b3cf780ad278eab55dc05a8ddeea2f7e00eae96f94762c5ffa8efb2bcaf2afcf1a4b0d5281062acf1b2ac5825fb7d7a3f83f18eb14dd2ee633c2 + languageName: node + linkType: hard + "@microsoft/api-extractor-model@npm:7.30.3": version: 7.30.3 resolution: "@microsoft/api-extractor-model@npm:7.30.3" @@ -1519,6 +1704,7 @@ __metadata: "@leafygreen-ui/inline-definition": "npm:^9.0.5" "@leafygreen-ui/leafygreen-provider": "npm:^5.0.2" "@leafygreen-ui/palette": "npm:^5.0.0" + "@leafygreen-ui/select": "npm:^16.2.0" "@leafygreen-ui/tokens": "npm:^3.2.1" "@leafygreen-ui/tooltip": "npm:^14.2.1" "@leafygreen-ui/typography": "npm:^22.1.0" @@ -2145,6 +2331,22 @@ __metadata: languageName: node linkType: hard +"@testing-library/dom@npm:9.3.1": + version: 9.3.1 + resolution: "@testing-library/dom@npm:9.3.1" + dependencies: + "@babel/code-frame": "npm:^7.10.4" + "@babel/runtime": "npm:^7.12.5" + "@types/aria-query": "npm:^5.0.1" + aria-query: "npm:5.1.3" + chalk: "npm:^4.1.0" + dom-accessibility-api: "npm:^0.5.9" + lz-string: "npm:^1.5.0" + pretty-format: "npm:^27.0.2" + checksum: 10c0/25d1deddba014c107fd9703181fbb7063ed376d3ad42d7918ee752e7e677edfb5abaf672b22afc5257ffe760c9c7e5cc981656297c328bc61578d23c6b65b4dc + languageName: node + linkType: hard + "@testing-library/dom@npm:^8.0.0": version: 8.20.1 resolution: "@testing-library/dom@npm:8.20.1" @@ -2504,6 +2706,15 @@ __metadata: languageName: node linkType: hard +"@types/react-is@npm:^18.0.0": + version: 18.3.1 + resolution: "@types/react-is@npm:18.3.1" + dependencies: + "@types/react": "npm:^18" + checksum: 10c0/c2a13c940c8dabc5fe38554f0b78560411a0618cc9b733c06d884b35f631b5c89eb88a016593df3b5bfd923517a337fd4a2f32598094f8924ac8e22b5f874c99 + languageName: node + linkType: hard + "@types/react-transition-group@npm:^4.4.5": version: 4.4.12 resolution: "@types/react-transition-group@npm:4.4.12" @@ -2523,6 +2734,16 @@ __metadata: languageName: node linkType: hard +"@types/react@npm:^18": + version: 18.3.26 + resolution: "@types/react@npm:18.3.26" + dependencies: + "@types/prop-types": "npm:*" + csstype: "npm:^3.0.2" + checksum: 10c0/7b62d91c33758f14637311921c92db6045b6328e2300666a35ef8130d06385e39acada005eaf317eee93228edc10ea5f0cd34a0385654d2014d24699a65bfeef + languageName: node + linkType: hard + "@types/resolve@npm:^1.20.2": version: 1.20.6 resolution: "@types/resolve@npm:1.20.6" @@ -7631,7 +7852,7 @@ __metadata: languageName: node linkType: hard -"polished@npm:^4.2.2": +"polished@npm:^4.1.3, polished@npm:^4.2.2": version: 4.3.1 resolution: "polished@npm:4.3.1" dependencies: @@ -7893,7 +8114,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^18.0.0, react-is@npm:^18.3.1": +"react-is@npm:^18.0.0, react-is@npm:^18.0.1, react-is@npm:^18.3.1": version: 18.3.1 resolution: "react-is@npm:18.3.1" checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072