#661 - Fix dropdowns

This commit is contained in:
Elio Struyf
2023-09-13 10:50:20 +02:00
parent f809fb2fe7
commit acc0a9ad19
7 changed files with 99 additions and 49 deletions
+1
View File
@@ -13,6 +13,7 @@
### 🐞 Fixes
- [#660](https://github.com/estruyf/vscode-front-matter/issues/660): Allow only to select unique content relationship values
- [#661](https://github.com/estruyf/vscode-front-matter/issues/661): Fixing the dropdowns when used at the bottom of a collapsible group
## [9.2.0] - 2023-09-11
@@ -9,6 +9,7 @@ import { FieldTitle } from './FieldTitle';
import { FieldMessage } from './FieldMessage';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import useDropdownStyle from '../../hooks/useDropdownStyle';
export interface IChoiceFieldProps extends BaseFieldProps<string | string[]> {
choices: string[] | Choice[];
@@ -27,6 +28,8 @@ export const ChoiceField: React.FunctionComponent<IChoiceFieldProps> = ({
}: React.PropsWithChildren<IChoiceFieldProps>) => {
const [crntSelected, setCrntSelected] = React.useState<string | string[] | null>(value);
const dsRef = React.useRef<Downshift<string> | null>(null);
const inputRef = React.useRef<HTMLInputElement | null>(null);
const { getDropdownStyle } = useDropdownStyle(inputRef as any);
const onValueChange = (txtValue: string) => {
if (multiSelect) {
@@ -105,6 +108,7 @@ export const ChoiceField: React.FunctionComponent<IChoiceFieldProps> = ({
{({ getToggleButtonProps, getItemProps, getMenuProps, isOpen, getRootProps }) => (
<div
{...getRootProps(undefined, { suppressRefError: true })}
ref={inputRef}
className={`metadata_field__choice`}
>
<button
@@ -118,11 +122,14 @@ export const ChoiceField: React.FunctionComponent<IChoiceFieldProps> = ({
</button>
<ul
className={`metadata_field__choice_list ${isOpen ? 'open' : 'closed'}`}
className={`field_dropdown metadata_field__choice_list ${isOpen ? 'open' : 'closed'}`}
style={{
bottom: getDropdownStyle(isOpen)
}}
{...getMenuProps()}
>
{isOpen
? availableChoices.map((choice, index) => (
{
availableChoices.map((choice, index) => (
<li
{...getItemProps({
key: getValue(choice, 'id'),
@@ -137,7 +144,7 @@ export const ChoiceField: React.FunctionComponent<IChoiceFieldProps> = ({
)}
</li>
))
: null}
}
</ul>
</div>
)}
@@ -11,6 +11,7 @@ import { CommandToCode } from '../../CommandToCode';
import { Page } from '../../../dashboardWebView/models';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import useDropdownStyle from '../../hooks/useDropdownStyle';
export interface IContentTypeRelationshipFieldProps extends BaseFieldProps<string | string[]> {
contentTypeName?: string;
@@ -34,6 +35,8 @@ export const ContentTypeRelationshipField: React.FunctionComponent<IContentTypeR
const [pages, setPages] = React.useState<Page[]>([]);
const [crntSelected, setCrntSelected] = React.useState<string | string[] | null>(value);
const dsRef = React.useRef<Downshift<string> | null>(null);
const inputRef = React.useRef<HTMLInputElement | null>(null);
const { getDropdownStyle } = useDropdownStyle(inputRef as any);
const onValueChange = (txtValue: string) => {
if (multiSelect) {
@@ -143,6 +146,7 @@ export const ContentTypeRelationshipField: React.FunctionComponent<IContentTypeR
{({ getToggleButtonProps, getItemProps, getMenuProps, isOpen, getRootProps }) => (
<div
{...getRootProps(undefined, { suppressRefError: true })}
ref={inputRef}
className={`metadata_field__choice`}
>
<button
@@ -156,11 +160,14 @@ export const ContentTypeRelationshipField: React.FunctionComponent<IContentTypeR
</button>
<ul
className={`metadata_field__choice_list ${isOpen ? 'open' : 'closed'}`}
className={`field_dropdown metadata_field__choice_list ${isOpen ? 'open' : 'closed'}`}
style={{
bottom: getDropdownStyle(isOpen)
}}
{...getMenuProps()}
>
{isOpen
? availableChoices.map((choice: Page, index) => (
{
availableChoices.map((choice: Page, index) => (
<li
{...getItemProps({
key: getValue(choice, contentTypeValue),
@@ -175,7 +182,7 @@ export const ContentTypeRelationshipField: React.FunctionComponent<IContentTypeR
)}
</li>
))
: null}
}
</ul>
</div>
)}
@@ -9,6 +9,7 @@ import { FieldTitle } from './FieldTitle';
import { FieldMessage } from './FieldMessage';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import useDropdownStyle from '../../hooks/useDropdownStyle';
export interface IDataFileFieldProps {
label: string;
@@ -36,6 +37,8 @@ export const DataFileField: React.FunctionComponent<IDataFileFieldProps> = ({
const [dataEntries, setDataEntries] = useState<string[] | null>(null);
const [crntSelected, setCrntSelected] = React.useState<string | string[] | null>();
const dsRef = React.useRef<Downshift<string> | null>(null);
const inputRef = React.useRef<HTMLInputElement | null>(null);
const { getDropdownStyle } = useDropdownStyle(inputRef as any);
const onValueChange = useCallback(
(txtValue: string) => {
@@ -149,6 +152,7 @@ export const DataFileField: React.FunctionComponent<IDataFileFieldProps> = ({
{({ getToggleButtonProps, getItemProps, getMenuProps, isOpen, getRootProps }) => (
<div
{...getRootProps(undefined, { suppressRefError: true })}
ref={inputRef}
className={`metadata_field__choice`}
>
<button
@@ -163,10 +167,13 @@ export const DataFileField: React.FunctionComponent<IDataFileFieldProps> = ({
<ul
className={`metadata_field__choice_list ${isOpen ? 'open' : 'closed'}`}
style={{
bottom: getDropdownStyle(isOpen)
}}
{...getMenuProps()}
>
{isOpen
? availableChoices.map((choice, index) => (
{
availableChoices.map((choice, index) => (
<li
{...getItemProps({
key: choice.id,
@@ -181,7 +188,7 @@ export const DataFileField: React.FunctionComponent<IDataFileFieldProps> = ({
)}
</li>
))
: null}
}
</ul>
</div>
)}
+8 -38
View File
@@ -15,6 +15,7 @@ import { PanelSettingsAtom } from '../state';
import { SparklesIcon } from '@heroicons/react/outline';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../localization';
import useDropdownStyle from '../hooks/useDropdownStyle';
export interface ITagPickerProps {
type: TagType;
@@ -58,6 +59,7 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
const [inputValue, setInputValue] = React.useState<string>('');
const prevSelected = usePrevious(crntSelected);
const inputRef = React.useRef<HTMLInputElement | null>(null);
const { getDropdownStyle } = useDropdownStyle(inputRef as any);
const dsRef = React.useRef<Downshift<string> | null>(null);
const settings = useRecoilValue(PanelSettingsAtom);
const [loading, setLoading] = React.useState<boolean>(false);
@@ -277,38 +279,6 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
);
}, [settings?.aiEnabled, label, type]);
const dropdownStyle = useCallback((isOpen) => {
if (isOpen && inputRef.current) {
const wrapper = inputRef.current.closest(".collapsible__body");
const dropdown = inputRef.current.parentElement?.parentElement?.querySelector('.article__tags__dropbox');
if (!wrapper || !dropdown) {
return undefined;
}
const wrapperStyles = getComputedStyle(wrapper);
const padding = parseInt(wrapperStyles.paddingTop) + parseInt(wrapperStyles.paddingBottom);
const wrapperHeight = wrapper.clientHeight - padding;
const tagPickerElm = inputRef.current.parentElement?.parentElement;
const dropdownHeight = dropdown?.clientHeight;
if (!tagPickerElm || !dropdownHeight) {
return undefined;
}
const tagPickerTop = tagPickerElm.offsetTop;
const fullHeight = tagPickerTop + dropdownHeight;
if (fullHeight > wrapperHeight) {
return "calc(100% - 38px)";
}
}
return undefined;
}, [inputRef]);
useEffect(() => {
setTimeout(() => {
triggerFocus();
@@ -322,7 +292,7 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
}, [crntSelected]);
return (
<div className={`article__tags`}>
<div className={`article__tags metadata_field`}>
{
loading && (
<div className='metadata_field__loading'>
@@ -405,19 +375,19 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
</div>
<ul
className={`article__tags__dropbox ${isOpen ? 'open' : 'closed'}`}
className={`field_dropdown article__tags__dropbox ${isOpen ? 'open' : 'closed'}`}
style={{
bottom: dropdownStyle(isOpen)
bottom: getDropdownStyle(isOpen)
}}
{...getMenuProps()}
>
{isOpen
? options
{
options
.filter((option) => filterList(option, inputValue))
.map((item, index) => (
<li {...getItemProps({ key: item, index, item })}>{item}</li>
))
: null}
}
</ul>
</>
)}
@@ -0,0 +1,53 @@
import { useCallback } from 'react';
export default function useDropdownStyle(inputRef: React.MutableRefObject<HTMLInputElement | null>) {
const bottomStyle = "calc(100% - 38px)";
const listItemHeight = 28;
const getDropdownStyle = useCallback((isOpen) => {
if (isOpen && inputRef.current) {
const wrapper = inputRef.current.closest(".collapsible__body");
const dropdown = inputRef.current.parentElement?.parentElement?.querySelector('.field_dropdown');
if (!wrapper || !dropdown) {
return undefined;
}
const wrapperStyles = getComputedStyle(wrapper);
const padding = parseInt(wrapperStyles.paddingTop) + parseInt(wrapperStyles.paddingBottom);
const wrapperHeight = wrapper.clientHeight - padding;
const dropdownElm: HTMLElement | null = inputRef.current.closest(".metadata_field");
const hasStyles = dropdown.getAttribute("style")?.includes(bottomStyle);
if (hasStyles) {
return bottomStyle;
}
let dropdownHeight = dropdown?.clientHeight;
if (dropdownHeight === 0) {
const listItems = dropdown?.querySelectorAll('li');
if (listItems && listItems.length > 0) {
dropdownHeight = listItems.length * listItemHeight;
}
}
if (!dropdownElm || !dropdownHeight) {
return undefined;
}
const tagPickerTop = dropdownElm.offsetTop;
const fullHeight = tagPickerTop + dropdownHeight;
if (fullHeight > wrapperHeight) {
return bottomStyle;
}
}
return undefined;
}, [inputRef]);
return {
getDropdownStyle
};
}
+5
View File
@@ -776,6 +776,11 @@ vscode-divider {
padding: 0.5rem;
}
/* Dropdown */
.field_dropdown.closed {
display: none;
}
/* Tags */
.article__tags {
position: relative;