diff --git a/package.json b/package.json index 70de2fc..73806be 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/typography": "^22.1.0", "@xyflow/react": "12.5.1", 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 50956e9..cec0aeb 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.1": + version: 25.1.1 + resolution: "@leafygreen-ui/button@npm:25.1.1" + dependencies: + "@leafygreen-ui/emotion": "npm:^5.0.4" + "@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.6" + "@leafygreen-ui/tokens": "npm:^3.2.4" + "@lg-tools/test-harnesses": "npm:^0.3.4" + polished: "npm:^4.2.2" + peerDependencies: + "@leafygreen-ui/leafygreen-provider": ">=3.2.0" + checksum: 10c0/fdf4b75599c4a3cf2c4125b0d3cad2aa423374a1133f15dfc94e4650ced4956509bb1689e714fa6e6760daab08b73a05b7511f0b0a95175a8cec63cb54b6b857 + languageName: node + linkType: hard + "@leafygreen-ui/emotion@npm:^5.0.0": version: 5.0.0 resolution: "@leafygreen-ui/emotion@npm:5.0.0" @@ -1113,6 +1142,33 @@ __metadata: languageName: node linkType: hard +"@leafygreen-ui/emotion@npm:^5.0.4": + version: 5.0.4 + resolution: "@leafygreen-ui/emotion@npm:5.0.4" + dependencies: + "@emotion/css": "npm:^11.1.3" + "@emotion/server": "npm:^11.4.0" + checksum: 10c0/9886c12e829c23345ea918d9ec212c025c1f8025c5aac7c69995c71fd51e300ae104e24b52ef33b195715257453976322d56a88cd32f4f226605e7ed0a3bec90 + languageName: node + linkType: hard + +"@leafygreen-ui/form-field@npm:^4.0.5": + version: 4.0.5 + resolution: "@leafygreen-ui/form-field@npm:4.0.5" + dependencies: + "@leafygreen-ui/emotion": "npm:^5.0.4" + "@leafygreen-ui/hooks": "npm:^9.2.1" + "@leafygreen-ui/icon": "npm:^14.6.0" + "@leafygreen-ui/lib": "npm:^15.6.2" + "@leafygreen-ui/palette": "npm:^5.0.2" + "@leafygreen-ui/tokens": "npm:^3.2.4" + "@leafygreen-ui/typography": "npm:^22.1.4" + peerDependencies: + "@leafygreen-ui/leafygreen-provider": ">=3.2.0" + checksum: 10c0/811c4243bde46f5053d783f809ae97a99f950ebfc75c247fe77dcdae143370c59f9fc7f6f2998a7bc7ebd478afd61fd9a4e42fe819daaa59890aa258e2e6b033 + languageName: node + linkType: hard + "@leafygreen-ui/hooks@npm:^9.1.1": version: 9.1.1 resolution: "@leafygreen-ui/hooks@npm:9.1.1" @@ -1135,6 +1191,17 @@ __metadata: languageName: node linkType: hard +"@leafygreen-ui/hooks@npm:^9.2.0, @leafygreen-ui/hooks@npm:^9.2.1": + version: 9.2.1 + resolution: "@leafygreen-ui/hooks@npm:9.2.1" + dependencies: + "@leafygreen-ui/lib": "npm:^15.6.2" + "@leafygreen-ui/tokens": "npm:^3.2.4" + lodash: "npm:^4.17.21" + checksum: 10c0/6ebeb476eab120d8b6f88b9a0095da1c2833cc64a744641bca56aa6641d8bbfcbadd25997999f677605a792fe24087d3ce58ed70ceaf6f90369d22fde8b72b43 + 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" @@ -1155,6 +1222,16 @@ __metadata: languageName: node linkType: hard +"@leafygreen-ui/icon@npm:^14.6.0": + version: 14.6.0 + resolution: "@leafygreen-ui/icon@npm:14.6.0" + dependencies: + "@leafygreen-ui/emotion": "npm:^5.0.3" + lodash: "npm:^4.17.21" + checksum: 10c0/1da867cdf73426429a11052b3412a458032dfa72c0fb862d989475ebca9c67b70636104715cb172a0d9d3fbbaa62feb224a71e3167cf7559cc38b9d7b5ce42db + 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" @@ -1170,6 +1247,23 @@ __metadata: languageName: node linkType: hard +"@leafygreen-ui/input-option@npm:^4.1.1": + version: 4.1.1 + resolution: "@leafygreen-ui/input-option@npm:4.1.1" + dependencies: + "@leafygreen-ui/a11y": "npm:^3.0.5" + "@leafygreen-ui/emotion": "npm:^5.0.4" + "@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:^3.2.4" + "@leafygreen-ui/typography": "npm:^22.1.4" + peerDependencies: + "@leafygreen-ui/leafygreen-provider": ">=3.2.0" + checksum: 10c0/8fed3fa3967b19ba26739cfba046746f07dcd2dcf5c680f2c72fa49292bea9f7dfc1edb8dcc35ce791219fa0e80fe0aa81d4314b7484fbfe3ea33fbac9aee98a + 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" @@ -1203,6 +1297,17 @@ __metadata: languageName: node linkType: hard +"@leafygreen-ui/lib@npm:^15.6.1, @leafygreen-ui/lib@npm:^15.6.2": + version: 15.6.2 + resolution: "@leafygreen-ui/lib@npm:15.6.2" + dependencies: + lodash: "npm:^4.17.21" + peerDependencies: + react: ^17.0.0 || ^18.0.0 + checksum: 10c0/f9a1460d4763e77211f12f6f15c07ca59a99622925da685bed4c2f874053e196b2941c4b4702e831401efc780b3ac4f4f08a3407e0483f1d2206d04d164288b0 + languageName: node + linkType: hard + "@leafygreen-ui/palette@npm:^5.0.0": version: 5.0.0 resolution: "@leafygreen-ui/palette@npm:5.0.0" @@ -1256,6 +1361,25 @@ __metadata: languageName: node linkType: hard +"@leafygreen-ui/popover@npm:^14.2.0": + version: 14.2.0 + resolution: "@leafygreen-ui/popover@npm:14.2.0" + dependencies: + "@floating-ui/react": "npm:^0.26.28" + "@leafygreen-ui/emotion": "npm:^5.0.4" + "@leafygreen-ui/hooks": "npm:^9.2.1" + "@leafygreen-ui/lib": "npm:^15.6.2" + "@leafygreen-ui/portal": "npm:^7.1.0" + "@leafygreen-ui/tokens": "npm:^3.2.4" + "@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/9c3257103ffe42084b742925158fae14d14aa4b588234960af796f73416fe0457675b1d230343808c94e5a2fe6b958e0d62026da1ebe4f32ab6bd7d693311b31 + languageName: node + linkType: hard + "@leafygreen-ui/portal@npm:^7.0.4": version: 7.0.4 resolution: "@leafygreen-ui/portal@npm:7.0.4" @@ -1268,6 +1392,53 @@ __metadata: languageName: node linkType: hard +"@leafygreen-ui/portal@npm:^7.1.0": + version: 7.1.0 + resolution: "@leafygreen-ui/portal@npm:7.1.0" + dependencies: + "@leafygreen-ui/hooks": "npm:^9.2.0" + "@leafygreen-ui/lib": "npm:^15.6.1" + peerDependencies: + react-dom: ^17.0.0 || ^18.0.0 + checksum: 10c0/84eee9ceac347eddeed8a9d267e602fc6078f9ee0ca77d283a80d3326bbb386423b1f10d0b9aa4e7c6deb49f4029dac5b598ba434ebebb2c3e8c6fbafb8d80c5 + languageName: node + linkType: hard + +"@leafygreen-ui/ripple@npm:^2.0.6": + version: 2.0.6 + resolution: "@leafygreen-ui/ripple@npm:2.0.6" + dependencies: + "@leafygreen-ui/tokens": "npm:^3.2.4" + checksum: 10c0/022b35bd5e5a66383ed338448a6032f34805f1618dbbe1ceba6d0ca3ab0c06e7e33bbb0c1e53c8b273c5c61bdc15304927b7c310dab403262a179623b92eab57 + languageName: node + linkType: hard + +"@leafygreen-ui/select@npm:^16.2.0": + version: 16.2.0 + resolution: "@leafygreen-ui/select@npm:16.2.0" + dependencies: + "@leafygreen-ui/button": "npm:^25.1.1" + "@leafygreen-ui/emotion": "npm:^5.0.4" + "@leafygreen-ui/form-field": "npm:^4.0.5" + "@leafygreen-ui/hooks": "npm:^9.2.1" + "@leafygreen-ui/icon": "npm:^14.6.0" + "@leafygreen-ui/input-option": "npm:^4.1.1" + "@leafygreen-ui/lib": "npm:^15.6.2" + "@leafygreen-ui/palette": "npm:^5.0.2" + "@leafygreen-ui/popover": "npm:^14.2.0" + "@leafygreen-ui/tokens": "npm:^3.2.4" + "@leafygreen-ui/typography": "npm:^22.1.4" + "@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/c901e7c15c9c397d02165087cb1c267c1ae437474b7b3eb3fb50eccec6b08a1966e0b2349961b66859b9dc8dc430ecc1eb3943327eaef6b5c987ad60ce315d53 + 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" @@ -1344,6 +1515,31 @@ __metadata: languageName: node linkType: hard +"@leafygreen-ui/typography@npm:^22.1.4": + version: 22.1.4 + resolution: "@leafygreen-ui/typography@npm:22.1.4" + dependencies: + "@leafygreen-ui/emotion": "npm:^5.0.4" + "@leafygreen-ui/icon": "npm:^14.6.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:^3.2.4" + peerDependencies: + "@leafygreen-ui/leafygreen-provider": ">=3.2.0" + checksum: 10c0/5730e7871192e8d487ddfb82740f6d1ad9dabb907258835cced9f21ce3e28e141d8a2c18385b599095719fdecdac5f3905e7806ec5e7230d7786696678df066a + 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" @@ -1410,6 +1606,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/typography": "npm:^22.1.0" "@storybook/addon-themes": "npm:^9.0.10" @@ -2035,6 +2232,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" @@ -2394,6 +2607,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" @@ -2413,6 +2635,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" @@ -7521,7 +7753,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: @@ -7783,7 +8015,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