mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
profile offers & needs wisgets
This commit is contained in:
parent
de7ea664d6
commit
c6be6abb84
88
src/Components/Input/Autocomplete.tsx
Normal file
88
src/Components/Input/Autocomplete.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@ -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;
|
||||
});
|
||||
}
|
||||
@ -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=""
|
||||
|
||||
96
src/Components/Profile/TagsWidget.tsx
Normal file
96
src/Components/Profile/TagsWidget.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@ -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%;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user