profile offers & needs wisgets

This commit is contained in:
Anton Tranelis 2024-02-11 14:00:21 +01:00
parent de7ea664d6
commit c6be6abb84
5 changed files with 211 additions and 47 deletions

View File

@ -0,0 +1,88 @@
import * as React from 'react'
import { useEffect } from 'react';
export const Autocomplete = ({ inputProps, suggestions, onSelected, pushFilteredSuggestions, setFocus }: { inputProps: any, suggestions: Array<any>, onSelected: (suggestion) => void, pushFilteredSuggestions?: Array<any>, setFocus?: boolean }) => {
const [filteredSuggestions, setFilteredSuggestions] = React.useState<Array<any>>([]);
const [heighlightedSuggestion, setHeighlightedSuggestion] = React.useState<number>(0);
useEffect(() => {
pushFilteredSuggestions && setFilteredSuggestions(pushFilteredSuggestions)
}, [pushFilteredSuggestions])
useEffect(() => {
setFocus && inputRef.current?.focus();
}, [setFocus])
const inputRef = React.useRef<HTMLInputElement>();
const getSuggestionValue = suggestion => suggestion.name;
// Use your imagination to render suggestions.
const renderSuggestion = suggestion => (
<div key={suggestion.name} className='tw-rounded-2xl tw-text-white tw-p-2 tw-px-4 tw-shadow-xl tw-card tw-h-[2.75em] tw-mt-3 tw-mr-4 tw-cursor-pointer tw-w-fit' style={{ backgroundColor: suggestion.color ? suggestion.color : "#666" }}>
<div className="tw-card-actions tw-justify-end">
</div><b>#{suggestion.name}</b>
</div>
);
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
return inputLength === 0 ? [] : suggestions.filter(tag =>
tag.name.toLowerCase().slice(0, inputLength) === inputValue
);
};
const handleChange = (e) => {
setFilteredSuggestions(getSuggestions(e.target.value))
// Call the parent's onChange handler, if it exists
if (inputProps.onChange) {
inputProps.onChange(e);
}
};
function handleSuggestionClick(suggestion) {
onSelected(suggestion)
}
const handleKeyDown = (event) => {
console.log(filteredSuggestions);
switch (event.key) {
case 'ArrowDown':
heighlightedSuggestion < filteredSuggestions.length-1 && setHeighlightedSuggestion(current => current +1)
break;
case 'ArrowUp':
heighlightedSuggestion>0 && setHeighlightedSuggestion(current => current -1)
break;
case 'Enter':
if(filteredSuggestions.length > 0) {
onSelected(filteredSuggestions[heighlightedSuggestion]);
setHeighlightedSuggestion(0);
}
filteredSuggestions.length == 0 && inputProps.onKeyDown(event);
break;
default:
inputProps.onKeyDown(event);
break;
}
}
return (
<div>
<input ref={inputRef} {...inputProps} type="text" onChange={(e) => handleChange(e)} onKeyDown={handleKeyDown}/>
<ul className='tw-absolute tw-z-[4000]'>
{filteredSuggestions.map((suggestion, index) => (
<li key={index} onClick={() => handleSuggestionClick(suggestion)}>{renderSuggestion(suggestion)}{index == heighlightedSuggestion && "+"}</li>
))}
</ul>
</div>
)
}

View File

@ -1,42 +1,14 @@
import * as React from "react";
import MDEditor from '@uiw/react-md-editor';
import rehypeSanitize from "rehype-sanitize";
import { useEffect } from "react";
export function TextEditor({ value, updateFormValue }: { value: string, updateFormValue: (string) => void }) {
useEffect(() => {
setHeightFromSourceToTarget("wmde-markdown-color","w-md-editor-text-input");
}, [value])
console.log(value);
console.log(updateFormValue);
return (
<div className="container">
<MDEditor
value={value}
onChange={(val) => updateFormValue(val)}
preview="edit"
height="100%"
previewOptions={{
rehypePlugins: [[rehypeSanitize]],
}}
/>
</div>
);
}
// TypeScript
const setHeightFromSourceToTarget = (sourceClassName: string, targetClassName: string): void => {
// Select the source element and get its height
const sourceElement = document.querySelector(`.${sourceClassName}`) as HTMLElement;
const height = sourceElement ? (sourceElement.clientHeight +10) + 'px' : '0'; // Convert to string to use in style
// Select all target elements
const targetElements = document.querySelectorAll(`.${targetClassName}`);
// Set the height of all target elements to match the source element's height
targetElements.forEach((element) => {
(element as HTMLElement).style.height = height;
});
}

View File

@ -1,6 +1,6 @@
import * as React from 'react'
import { useItems, useUpdateItem } from '../Map/hooks/useItems'
import { useState } from 'react';
import { useState } from 'react';
import { getValue } from '../../Utils/GetValue';
import ReactCrop, { Crop, centerCrop, makeAspectCrop } from 'react-image-crop';
import { toast } from 'react-toastify';
@ -15,6 +15,7 @@ import { randomColor } from '../../Utils/RandomColor';
import { useNavigate } from 'react-router-dom';
import { UserItem } from '../../types';
import { MapOverlayPage } from '../Templates';
import { TagsWidget } from './TagsWidget';
export function OverlayProfileSettings() {
@ -215,18 +216,24 @@ export function OverlayProfileSettings() {
<div role="tablist" className="tw-tabs tw-tabs-lifted tw-mt-4">
<input type="radio" name="my_tabs_2" role="tab" className={`tw-tab [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]`} aria-label="Text" checked={activeTab == 1 && true} onChange={() => setActiveTab(1)} />
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-border-[var(--fallback-bc,oklch(var(--bc)/0.2))] tw-rounded-box tw-h-[calc(100dvh-332px)] tw-min-h-56">
<TextAreaInput placeholder="About me, Contact, #Tags, ..." defaultValue={user?.description ? user.description : ""} updateFormValue={(v) => setText(v)} containerStyle='tw-h-full' inputStyle='tw-h-full'/>
<TextAreaInput placeholder="About me, Contact, #Tags, ..." defaultValue={user?.description ? user.description : ""} updateFormValue={(v) => setText(v)} containerStyle='tw-h-full' inputStyle='tw-h-full tw-border-t-0' />
</div>
<input type="radio" name="my_tabs_2" role="tab" className="tw-tab tw-min-w-[10em] [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]" aria-label="Offers & Needs" checked={activeTab == 2 && true} onChange={() => setActiveTab(2)} />
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-pt-6 tw-h-[calc(100dvh-332px)]">
<textarea className="tw-textarea tw-textarea-bordered tw-w-full tw-mb-4 tw-h-32 " placeholder="Offers"></textarea>
<textarea className="tw-textarea tw-textarea-bordered tw-w-full tw-h-32 " placeholder="Needs"></textarea>
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-pt-4 tw-h-[calc(100dvh-332px)] tw-min-h-56">
<div className='tw-h-full'>
<div className='tw-w-full tw-h-[calc(50%-0.75em)] tw-mb-4'>
<TagsWidget placeholder="enter your offers" containerStyle='tw-bg-transparent tw-w-full tw-h-full tw-mt-3 tw-text-xs tw-h-[calc(100%-1rem)] tw-min-h-[5em] tw-pb-2'/>
</div>
<div className='tw-w-full tw-h-[calc(50%-0.75em)] '>
<TagsWidget placeholder="enter your needs" containerStyle='tw-bg-transparent tw-w-full tw-h-full tw-mt-3 tw-text-xs tw-h-[calc(100%-1rem)] tw-min-h-[5em] tw-pb-2'/>
</div>
</div>
</div>
<input type="radio" name="my_tabs_2" role="tab" className="tw-tab [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]" aria-label="Contact" checked={activeTab == 3 && true} onChange={() => setActiveTab(3)} />
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-pt-6 tw-h-[calc(100dvh-332px)] tw-overflow-y-auto">
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-pt-4 tw-h-[calc(100dvh-332px)] tw-min-h-56">
<div className='tw-overflow-y-auto tw-h-full tw-min-h-56"'>
<input className='tw-input tw-mb-2 tw-input-bordered tw-w-full' placeholder='E-Mail ...'></input>
<input className='tw-input tw-mb-2 tw-input-bordered tw-w-full' placeholder='Telefon ...'></input>
<input className='tw-input tw-mb-2 tw-input-bordered tw-w-full' placeholder='Webpage ...'></input>
@ -234,13 +241,14 @@ export function OverlayProfileSettings() {
<input className='tw-input tw-mb-2 tw-input-bordered tw-w-full' placeholder='Telegram ...'></input>
<input className='tw-input tw-mb-2 tw-input-bordered tw-w-full' placeholder='Instagram ...'></input>
<input className='tw-input tw-mb-2 tw-input-bordered tw-w-full' placeholder='Twitter ...'></input>
</div>
</div>
</div>
<div className="tw-mt-4 tw-mb-4"><button className={loading ? " tw-loading tw-btn-disabled tw-btn tw-btn-primary tw-float-right" : "tw-btn tw-btn-primary tw-float-right"} onClick={() => onUpdateUser()}>Update</button></div>
</div>
<div className="tw-mt-2"><button className={loading ? " tw-loading tw-btn-disabled tw-btn tw-btn-primary tw-float-right" : "tw-btn tw-btn-primary tw-float-right"} onClick={() => onUpdateUser()}>Update</button></div>
</MapOverlayPage>
<DialogModal
title=""

View File

@ -0,0 +1,96 @@
import * as React from 'react'
import { useState } from 'react';
import { useTags } from '../Map/hooks/useTags';
import { Tag } from '../../types';
import { Autocomplete } from '../Input/Autocomplete';
import { randomColor } from '../../Utils/RandomColor';
export const TagsWidget = ({placeholder, containerStyle}) => {
const [input, setInput] = useState('');
const [localTags, setLocalTags] = useState<Array<Tag>>([]);
const [isKeyReleased, setIsKeyReleased] = useState(false);
const tags = useTags();
const [pushFilteredSuggestions, setPushFilteredSuggestions] = useState<Array<any>>([]);
const [focusInput, setFocusInput] = useState<boolean>(false);
const onChange = (e) => {
const { value } = e.target;
setInput(value);
};
const onKeyDown = (e) => {
const { key } = e;
const trimmedInput = input.trim();
if ((key === 'Enter' || key === ',' ) && trimmedInput.length && !localTags.some(tag => tag.name.toLocaleLowerCase() === trimmedInput.toLocaleLowerCase())) {
e.preventDefault();
const newTag = tags.find(t => t.name === trimmedInput.toLocaleLowerCase())
newTag && setLocalTags(prevState => [...prevState, newTag]);
!newTag && setLocalTags(prevState => [...prevState, { id: crypto.randomUUID(), name: trimmedInput.toLocaleLowerCase(), color: randomColor() }]);
setInput('');
setPushFilteredSuggestions([]);
}
if (key === "Backspace" && !input.length && localTags.length && isKeyReleased) {
const localTagsCopy = [...localTags];
const poppedTag = localTagsCopy.pop();
e.preventDefault();
setLocalTags(localTagsCopy);
poppedTag && setInput(poppedTag.name);
}
setIsKeyReleased(false);
};
const onKeyUp = () => {
setIsKeyReleased(true);
}
const deleteTag = (tag) => {
setLocalTags(prevState => prevState.filter((t) => t !== tag))
}
const onSelected = (tag) => {
if(!localTags.some(t => t.name.toLocaleLowerCase() === tag.name.toLocaleLowerCase())) {
const newTag = tags.find(t => t.name === tag.name.toLocaleLowerCase())
newTag && setLocalTags(prevState => [...prevState, newTag]);
!newTag && setLocalTags(prevState => [...prevState, { id: crypto.randomUUID(), name: tag.name.toLocaleLowerCase(), color: randomColor() }]);
setInput('');
setPushFilteredSuggestions([]);
}
}
const inputProps = {
value: input,
placeholder: placeholder,
onKeyDown: onKeyDown,
onKeyUp: onKeyUp,
onChange: onChange,
className: 'tw-bg-transparent tw-w-fit tw-mt-5 tw-h-fit'
}
return (
<div onClick={()=> {
setFocusInput(true);
setTimeout(()=> {
setFocusInput(false)
}, 200)
}} className={`tw-input tw-input-bordered tw-cursor-text ${containerStyle}`}>
<div className='tw-flex tw-flex-wrap tw-h-fit'>
{localTags.map((tag) => (
<div key={tag.name} className='tw-rounded-2xl tw-text-white tw-p-2 tw-px-4 tw-shadow-xl tw-card tw-h-[2.75em] tw-mt-3 tw-mr-4' style={{ backgroundColor: tag.color ? tag.color : "#666" }}>
<div className="tw-card-actions tw-justify-end">
<label className="tw-btn tw-btn-xs tw-btn-circle tw-absolute tw--right-2 tw--top-2 tw-bg-white tw-text-gray-600" onClick={() => (deleteTag(tag))}></label>
</div><b>#{tag.name}</b>
</div>
))}
<Autocomplete suggestions={tags} pushFilteredSuggestions={pushFilteredSuggestions} setFocus={focusInput} inputProps={inputProps} onSelected={(tag) => onSelected(tag)}/>
</div>
</div>
)
}

View File

@ -35,6 +35,14 @@
color: theme('colors.base-content');
}
.Toastify__toast-container {
z-index: 1999 !important;
}
.Toastify__toast-container--top-right {
top: 4.75em !important;
}
:root {
--toastify-color-info: theme('colors.info');
@ -67,11 +75,3 @@ input[type="file"] {
.tw-tab-content .container {
height: 100%;
}
.w-md-editor-text {
height: 100%;
}
.w-md-editor-text-input {
min-height: 100%;
}