mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-07 01:13:08 +02:00
#661 - Fix dropdowns
This commit is contained in:
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -776,6 +776,11 @@ vscode-divider {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
/* Dropdown */
|
||||
.field_dropdown.closed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Tags */
|
||||
.article__tags {
|
||||
position: relative;
|
||||
|
||||
Reference in New Issue
Block a user