Skip to content

Commit

Permalink
feat: first version
Browse files Browse the repository at this point in the history
first version
  • Loading branch information
815are committed Oct 17, 2024
1 parent 550e708 commit 30a3893
Show file tree
Hide file tree
Showing 11 changed files with 706 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.dropdown-item-input {
input {
display: block !important;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import type { ReactElement } from 'react';
import { UITextInput } from '../../UIInput';
import type { UITextInputProps } from '../../UIInput';
import { UIContextualMenuItem } from '../../UIContextualMenu';
import { UISelectableOptionWithSubValues } from './types';
import { RenamedEntry } from './utils';
import { useEditValue } from './hooks';

import './ItemInput.scss';

export interface ItemInputProps extends UITextInputProps {
option?: UISelectableOptionWithSubValues;
renamedEntry?: string;
}

export interface ItemInputRef {
setOption: (option: UISelectableOptionWithSubValues) => void;
}

function getSubValueText(option?: UISelectableOptionWithSubValues): string | undefined {
return option?.subValue?.text ?? option?.text;
}

function ItemInputComponent(props: ItemInputProps, ref: React.ForwardedRef<ItemInputRef>): React.ReactElement {
const { option, renamedEntry, ...inputProps } = props;
const { value, onChange, onClick } = inputProps;
const [subValue, setSubValue] = useState<string | undefined>(getSubValueText(option));
const [localValue, placeholder, setLocalValue] = useEditValue('', value, renamedEntry);

useImperativeHandle<ItemInputRef, ItemInputRef>(ref, () => ({
setOption: (option: UISelectableOptionWithSubValues) => {
setSubValue(getSubValueText(option));
}
}));

useEffect(() => {
setSubValue(getSubValueText(option));
}, [option]);

const onLocalChange = (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => {
setLocalValue(newValue ?? '');
onChange?.(event, newValue);
};

return (
<div style={{ display: 'flex', width: '100%' }}>
<UITextInput
className="dropdown-item-input"
onMouseDown={(event) => {
console.log('mouse down!!');
const target = event.target as HTMLElement;
(document.activeElement as HTMLElement)?.blur();
target.focus();
event.stopPropagation();
}}
{...inputProps}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
onClick?.(event);
}}
onChange={onLocalChange}
placeholder={placeholder}
value={!placeholder ? localValue : ''}
/>
{subValue}
</div>
);
}
export const ItemInput = forwardRef(ItemInputComponent);

ItemInput.displayName = 'ItemInput';
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import React, { Ref, useEffect, useRef, useState } from 'react';
import { UIComboBox } from '../UIComboBox';
import type { UIComboBoxProps, UIComboBoxRef, UISelectableOption } from '../UIComboBox';
import type { IComboBoxOption, ISelectableOption } from '@fluentui/react';
import { UITextInput } from '../../UIInput';
import { UIContextualMenu, UIContextualMenuItem } from '../../UIContextualMenu';
import { useOptions, useSelectedKey } from './hooks';
import { UISelectableOptionWithSubValues } from './types';
import { ItemInput, ItemInputRef } from './ItemInput';
import { isValueValid, RenamedEntries, resolveValue, resolveValueForOption, updateEditableEntry } from './utils';

export interface UIComboboxTestProps extends UIComboBoxProps {
/**
* Collection of options for this ComboBox.
*/
options: UISelectableOptionWithSubValues[];
}

interface SubMenuData {
target: HTMLElement | null;
option?: UISelectableOptionWithSubValues;
}

function getOption(
options: UISelectableOptionWithSubValues[],
key?: string | number
): UISelectableOptionWithSubValues | undefined {
return options.find((option) => option.key === key);
}

export const UIComboBoxDummy = (props: UIComboboxTestProps) => {
const { options, onChange } = props;
const [selectedKey, setSelectedKey, convertedOptions] = useOptions(props.selectedKey, options);
const [subMenu, setSubMenu] = useState<SubMenuData | null>(null);
const { target, option: activeOption } = subMenu ?? {};
const inputItemRefs = useRef<{ [key: string]: ItemInputRef | null }>({});
const [_pendingText, setPendingText] = useState<string | undefined>(undefined);
// console.log('UIComboBoxDummy -> ' + selectedKey);

const triggerChange = (
event: React.FormEvent<UIComboBoxRef>,
selectedOption?: UISelectableOptionWithSubValues,
index?: number,
value?: string
) => {
if (selectedOption) {
setSelectedKey(selectedOption.key);
}
const option = getOption(convertedOptions, selectedOption?.key);
const resolveValue = option ? resolveValueForOption(option) : selectedOption?.key.toString();
// console.log(resolveValue);
if (selectedOption?.editable && option && !isValueValid(option)) {
console.log('Invalid!!!');
onChange?.(event, undefined, undefined, '');
} else {
console.log('valid!!!');
onChange?.(
event,
selectedOption ? { ...selectedOption, key: resolveValue ?? selectedOption.key } : undefined,
index,
resolveValue ?? value
);
}

// Close submenu
setSubMenu(null);
};

return (
<>
<UIComboBox
{...(props as any)}
onChange={(
event: React.FormEvent<UIComboBoxRef>,
selectedOption?: UISelectableOptionWithSubValues,
index?: number,
value?: string
) => {
console.log('onchange!!!');

triggerChange(event, selectedOption, index, value);
}}
onItemClick={(
event: React.FormEvent<UIComboBoxRef>,
selectedOption?: UISelectableOptionWithSubValues,
index?: number
) => {
console.log('onItemClick!!! ' + index);

triggerChange(event, selectedOption, index);
}}
selectedKey={selectedKey}
options={convertedOptions}
onMenuOpen={() => {
console.log('onMenuOpen');
}}
onMenuDismiss={() => {
console.log('onMenuDismiss');
setSubMenu(null);
}}
calloutProps={{
preventDismissOnEvent(event) {
console.log('preventDismissOnEvent ' + event.type);
let prevent = false;
if (event.type === 'focus' || event.type === 'click') {
const target = event.target as HTMLElement;
prevent = !!(
target.closest('.dropdown-submenu') || target.querySelector('.dropdown-submenu')
);
}
console.log('preventDismissOnEvent ' + prevent);
return prevent;
}
}}
onRenderList={(
props?: UISelectableOptionWithSubValues,
defaultRender?: (props?: UISelectableOptionWithSubValues) => JSX.Element | null
) => {
return (
<div
onMouseOver={(event) => {
const target = event.target as HTMLElement;
const element = target.closest('[data-index]') as HTMLElement;
if (element) {
const index = element.getAttribute('data-index');
if (index !== null) {
setSubMenu({
target: element,
option: convertedOptions[parseInt(index)]
});
}
}
}}>
{defaultRender?.(props)}
</div>
);
}}
onRenderOption={(
props?: UISelectableOptionWithSubValues,
defaultRender?: (props?: UISelectableOptionWithSubValues) => JSX.Element | null
) => {
console.log(props);
if (props?.editable) {
const { subValue } = props;
const option = getOption(convertedOptions, props?.key);
return (
<ItemInput
ref={(ref) => {
inputItemRefs.current[props.key.toString()] = ref;
}}
renamedEntry={option?.text}
onChange={(
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
value?: string
) => {
if (option) {
option.text = value ?? option.text;
}
setPendingText(value);
}}
onClick={() => {
setSelectedKey(props.key);
}}
option={props}
/>
);
}
return defaultRender?.(props);
}}
// onRenderItem={(
// props?: IComboBoxExtendsOption,
// defaultRender?: (props?: IComboBoxExtendsOption) => JSX.Element | null
// ) => {
// console.log('external onRenderItem');
// return defaultRender?.(props);
// }}
// onChange={() => {
// console.log('change');
// }}
/>
{target && activeOption?.options && (
<UIContextualMenu
target={target}
className="dropdown-submenu"
onRestoreFocus={() => {
// No focus restore
}}
calloutProps={{
// popupProps: {
// ref: calloutRef
// },
onMouseLeave: (event) => {
setSubMenu(null);
}
// onPositioned: (positions) => {
// calloutPosition.current =
// positions?.elementPosition.left ?? positions?.elementPosition.right;
// }
}}
onItemClick={(ev, item?: UIContextualMenuItem) => {
if (activeOption && item) {
activeOption.subValue = item;
inputItemRefs.current[activeOption.key]?.setOption(activeOption);
}
setSubMenu(null);
}}
directionalHint={11}
shouldFocusOnMount={false}
items={activeOption.options}
/>
)}
</>
);
};
Loading

0 comments on commit 30a3893

Please sign in to comment.