mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
basic calendart ui and tabs in profile
This commit is contained in:
parent
824075af2e
commit
c15f7a897d
10
package-lock.json
generated
10
package-lock.json
generated
@ -13,6 +13,7 @@
|
||||
"@tanstack/react-query": "^5.17.8",
|
||||
"@types/offscreencanvas": "^2019.7.1",
|
||||
"axios": "^1.6.5",
|
||||
"date-fns": "^3.3.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"leaflet.locatecontrol": "^0.79.0",
|
||||
"prop-types": "^15.8.1",
|
||||
@ -1308,6 +1309,15 @@
|
||||
"url": "https://opencollective.com/daisyui"
|
||||
}
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz",
|
||||
"integrity": "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/kossnocorp"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
|
||||
@ -46,6 +46,7 @@
|
||||
"@tanstack/react-query": "^5.17.8",
|
||||
"@types/offscreencanvas": "^2019.7.1",
|
||||
"axios": "^1.6.5",
|
||||
"date-fns": "^3.3.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"leaflet.locatecontrol": "^0.79.0",
|
||||
"prop-types": "^15.8.1",
|
||||
|
||||
@ -9,11 +9,17 @@ import { getValue } from '../../../../Utils/GetValue';
|
||||
import remarkBreaks from 'remark-breaks'
|
||||
import { decodeTag } from '../../../../Utils/FormatTags';
|
||||
|
||||
export const TextView = ({ item, truncate = false}: { item?: Item, truncate?: boolean }) => {
|
||||
export const TextView = ({ item, truncate = false, itemTextField}: { item?: Item, truncate?: boolean,itemTextField?: string }) => {
|
||||
const tags = useTags();
|
||||
const addFilterTag = useAddFilterTag();
|
||||
|
||||
let text = item?.layer?.itemTextField && item ? getValue(item, item.layer?.itemTextField) : "";
|
||||
console.log(item);
|
||||
|
||||
|
||||
let text = "";
|
||||
|
||||
if(itemTextField && item) text = getValue(item, itemTextField);
|
||||
else text = item?.layer?.itemTextField && item ? getValue(item, item.layer?.itemTextField) : ""
|
||||
|
||||
if(item && text && truncate) text = truncateString(text, 100, true);
|
||||
|
||||
|
||||
@ -31,6 +31,8 @@ export function OverlayProfile() {
|
||||
const [offers, setOffers] = useState<Array<Tag>>([]);
|
||||
const [needs, setNeeds] = useState<Array<Tag>>([]);
|
||||
|
||||
const [activeTab, setActiveTab] = useState<number>(1);
|
||||
|
||||
|
||||
const addFilterTag = useAddFilterTag();
|
||||
|
||||
@ -85,8 +87,21 @@ export function OverlayProfile() {
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className='tw-overflow-y-auto tw-h-full tw-pt-4 fade'>
|
||||
<div className='tw-grid tw-grid-cols-1'>
|
||||
|
||||
|
||||
|
||||
<div className='tw-h-full'>
|
||||
|
||||
<div role="tablist" className="tw-tabs tw-tabs-lifted tw-mt-2">
|
||||
<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="Vision" checked={activeTab == 1 && true} onChange={() => setActiveTab(1)} />
|
||||
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-268px)] tw-min-h-56 tw-overflow-y-auto fade tw-pt-2">
|
||||
<TextView item={item} />
|
||||
</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-4 tw-h-[calc(100dvh-332px)] tw-min-h-56">
|
||||
<div className='tw-h-full'>
|
||||
<div className='tw-grid tw-grid-cols-1'>
|
||||
{
|
||||
offers.length > 0 ?
|
||||
<div className='tw-col-span-1'>
|
||||
@ -110,7 +125,18 @@ export function OverlayProfile() {
|
||||
</div> : ""
|
||||
}
|
||||
</div>
|
||||
<TextView item={item} />
|
||||
</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-h-[calc(100dvh-268px)] tw-min-h-56 tw-overflow-y-auto fade tw-pt-2">
|
||||
<TextView item={item} itemTextField='user_created.contact' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
@ -30,6 +30,7 @@ export function OverlayProfileSettings() {
|
||||
const [color, setColor] = useState<string>("");
|
||||
const [offers, setOffers] = useState<Array<Tag>>([]);
|
||||
const [needs, setNeeds] = useState<Array<Tag>>([]);
|
||||
const [contact, setContact] = useState<string>("");
|
||||
|
||||
const [activeTab, setActiveTab] = useState<number>(1);
|
||||
|
||||
@ -63,6 +64,7 @@ export function OverlayProfileSettings() {
|
||||
const need = tags.find(t => t.id === o.tags_id);
|
||||
need && setNeeds(current => [...current,need])
|
||||
})
|
||||
setContact(user?.contact ? user.contact : "");
|
||||
}, [user])
|
||||
|
||||
const imgRef = React.useRef<HTMLImageElement>(null)
|
||||
@ -180,7 +182,7 @@ export function OverlayProfileSettings() {
|
||||
});
|
||||
|
||||
|
||||
changedUser = { id: id, first_name: name, description: text, color: color, ...avatar.length > 10 && { avatar: avatar }, ... offers.length > 0 && {offers: offer_updates}, ... needs.length > 0 && {needs: needs_updates} };
|
||||
changedUser = { id: id, first_name: name, description: text, contact: contact, color: color, ...avatar.length > 10 && { avatar: avatar }, ... offers.length > 0 && {offers: offer_updates}, ... needs.length > 0 && {needs: needs_updates} };
|
||||
// update profile item in current state
|
||||
const item = items.find(i => i.layer?.itemOwnerField && getValue(i, i.layer?.itemOwnerField).id === id);
|
||||
|
||||
@ -259,9 +261,9 @@ 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)} />
|
||||
<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="Vision" 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 tw-border-t-0 tw-rounded-tl-none' />
|
||||
<TextAreaInput placeholder="My Vision..." defaultValue={user?.description ? user.description : ""} updateFormValue={(v) => setText(v)} containerStyle='tw-h-full' inputStyle='tw-h-full tw-border-t-0 tw-rounded-tl-none' />
|
||||
</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)} />
|
||||
@ -277,16 +279,8 @@ export function OverlayProfileSettings() {
|
||||
</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-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>
|
||||
<input className='tw-input tw-mb-2 tw-input-bordered tw-w-full' placeholder='Matrix ... '></input>
|
||||
<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 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="Contact ..." defaultValue={user?.contact ? user.contact : ""} updateFormValue={(v) => setContact(v)} containerStyle='tw-h-full' inputStyle='tw-h-full tw-border-t-0 tw-rounded-tl-none' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
33
src/Components/Templates/CircleLayout.tsx
Normal file
33
src/Components/Templates/CircleLayout.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
export const CircleLayout = ({ items,radius, fontSize }) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
const itemCount = items.length;
|
||||
|
||||
if (container) {
|
||||
for (let i = 0; i < itemCount; i++) {
|
||||
const startAngle = (Math.PI) / 2;
|
||||
const angle = startAngle + (i / itemCount) * (2 * Math.PI);
|
||||
const x = radius * Math.cos(angle);
|
||||
const y = radius * Math.sin(angle);
|
||||
const child = container.children[i] as HTMLElement;
|
||||
child.style.transform = `translate(${x}px, ${y}px)`;
|
||||
}
|
||||
}
|
||||
}, [items]);
|
||||
|
||||
return (
|
||||
<div className="tw-absolute tw-mx-auto tw-flex tw-justify-center tw-items-center tw-h-full tw-w-full" ref={containerRef}>
|
||||
{items.map((item, index) => (
|
||||
<div key={index} className="tw-absolute" style={{fontSize: fontSize}}>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -13,6 +13,8 @@ export function MapOverlayPage({ children, className, backdrop }: { children: Re
|
||||
const navigate = useNavigate();
|
||||
|
||||
const overlayRef = React.createRef<HTMLDivElement>()
|
||||
const backdropRef = React.createRef<HTMLDivElement>()
|
||||
|
||||
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -20,12 +22,16 @@ export function MapOverlayPage({ children, className, backdrop }: { children: Re
|
||||
L.DomEvent.disableClickPropagation(overlayRef.current)
|
||||
L.DomEvent.disableScrollPropagation(overlayRef.current)
|
||||
}
|
||||
}, [overlayRef])
|
||||
if(backdropRef.current !== null && backdrop) {
|
||||
L.DomEvent.disableClickPropagation(backdropRef.current)
|
||||
L.DomEvent.disableScrollPropagation(backdropRef.current)
|
||||
}
|
||||
}, [overlayRef, backdropRef])
|
||||
|
||||
|
||||
return (
|
||||
<div className={`tw-absolute tw-h-full tw-w-full tw-m-auto ${backdrop && "tw-z-[2000]"}`}>
|
||||
<div className={`${backdrop && "tw-backdrop-brightness-75"} tw-h-full tw-w-full tw-grid tw-place-items-center tw-m-auto`} >
|
||||
<div ref={backdropRef} className={`${backdrop && "tw-backdrop-brightness-75"} tw-h-full tw-w-full tw-grid tw-place-items-center tw-m-auto`} >
|
||||
<div ref={overlayRef} className={`tw-card tw-shadow-xl tw-bg-base-100 tw-p-4 ${className && className} ${!backdrop && "tw-z-[2000]"} tw-absolute tw-top-0 tw-bottom-0 tw-right-0 tw-left-0 tw-m-auto`}>
|
||||
<div className="tw-card-body tw-p-2 tw-h-full">
|
||||
{children}
|
||||
|
||||
95
src/Components/Templates/MoonCalendar.tsx
Normal file
95
src/Components/Templates/MoonCalendar.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
import * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
add,
|
||||
addDays,
|
||||
eachDayOfInterval,
|
||||
endOfMonth,
|
||||
endOfWeek,
|
||||
format,
|
||||
getDay,
|
||||
isSameMonth,
|
||||
isToday,
|
||||
parse,
|
||||
startOfToday,
|
||||
startOfWeek,
|
||||
sub,
|
||||
} from "date-fns";
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
|
||||
import { MapOverlayPage } from './MapOverlayPage';
|
||||
import { CircleLayout } from './CircleLayout';
|
||||
import { LUNAR_MONTH, getLastNewMoon, getNextNewMoon } from '../../Utils/Moon';
|
||||
|
||||
|
||||
export const MoonCalendar = () => {
|
||||
const today = startOfToday();
|
||||
const days = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
|
||||
const colStartClasses = [
|
||||
"",
|
||||
"col-start-2",
|
||||
"col-start-3",
|
||||
"col-start-4",
|
||||
"col-start-5",
|
||||
"col-start-6",
|
||||
"col-start-7",
|
||||
];
|
||||
|
||||
const [currMonth, setCurrMonth] = useState(() => format(today, "MMM-yyyy"));
|
||||
let firstDayOfMonth = parse(currMonth, "MMM-yyyy", new Date());
|
||||
|
||||
const daysInMonth = eachDayOfInterval({
|
||||
start: firstDayOfMonth,
|
||||
end: endOfMonth(firstDayOfMonth),
|
||||
});
|
||||
|
||||
const currentMoonCycle = eachDayOfInterval({
|
||||
start: getLastNewMoon(),
|
||||
end: getNextNewMoon()
|
||||
});
|
||||
|
||||
|
||||
const getPrevMonth = (event: React.MouseEvent<SVGSVGElement>) => {
|
||||
event.preventDefault();
|
||||
const firstDayOfPrevMonth = add(firstDayOfMonth, { months: -1 });
|
||||
setCurrMonth(format(firstDayOfPrevMonth, "MMM-yyyy"));
|
||||
};
|
||||
|
||||
const getNextMonth = (event: React.MouseEvent<SVGSVGElement>) => {
|
||||
event.preventDefault();
|
||||
const firstDayOfNextMonth = add(firstDayOfMonth, { months: 1 });
|
||||
setCurrMonth(format(firstDayOfNextMonth, "MMM-yyyy"));
|
||||
};
|
||||
|
||||
return (
|
||||
<MapOverlayPage backdrop className='tw-h-96 tw-w-80'>
|
||||
<p className='tw-self-center tw-text-lg tw-font-bold'>Moon Cycle</p>
|
||||
<div className='tw-relative tw-h-full'>
|
||||
<CircleLayout items={["🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"]} radius={80} fontSize={"3em"} />
|
||||
<CircleLayout items={ [
|
||||
format(getLastNewMoon(), "dd.MM hh:mm"),
|
||||
format(sub(getNextNewMoon(), {seconds: LUNAR_MONTH*86400/4*3}), "dd.MM hh:mm"),
|
||||
format(sub(getNextNewMoon(), {seconds: LUNAR_MONTH*86400/2}), "dd.MM hh:mm"),
|
||||
format(sub(getNextNewMoon(), {seconds: LUNAR_MONTH*86400/4}), "dd.MM hh:mm")
|
||||
]} radius={120} fontSize={"0.8em"}/>
|
||||
</div>
|
||||
<div className='tw-flex tw-flex-row'>
|
||||
<ChevronLeftIcon
|
||||
className="tw-w-6 tw-h-6 tw-cursor-pointer"
|
||||
onClick={getPrevMonth}
|
||||
/>
|
||||
<p className='tw-text-center tw-p-1 tw-h-full tw-grow'>from {format(getLastNewMoon(), "dd.MM")} - to {format(getNextNewMoon(), "dd.MM")}</p>
|
||||
|
||||
<ChevronRightIcon
|
||||
className="tw-w-6 tw-h-6 tw-cursor-pointer"
|
||||
onClick={getNextMonth}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
</MapOverlayPage>
|
||||
);
|
||||
}
|
||||
|
||||
const capitalizeFirstLetter = (string: string) => {
|
||||
return string
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
export {CardPage} from './CardPage'
|
||||
export {TitleCard} from './TitleCard'
|
||||
export {MapOverlayPage} from './MapOverlayPage'
|
||||
export {MapOverlayPage} from './MapOverlayPage'
|
||||
export {CircleLayout} from './CircleLayout'
|
||||
export {MoonCalendar} from './MoonCalendar'
|
||||
32
src/Utils/Moon.ts
Normal file
32
src/Utils/Moon.ts
Normal file
@ -0,0 +1,32 @@
|
||||
export const LUNAR_MONTH: number = 29.530588853;
|
||||
|
||||
const getJulianDate = (date: Date = new Date()): number => {
|
||||
const time: number = date.getTime();
|
||||
const tzoffset: number = date.getTimezoneOffset();
|
||||
return (time / 86400000) - (tzoffset / 1440) + 2440587.5;
|
||||
};
|
||||
|
||||
const normalize = (value: number): number => {
|
||||
value = value - Math.floor(value);
|
||||
if (value < 0) value += 1;
|
||||
return value;
|
||||
};
|
||||
|
||||
const getLunarAgePercent = (date: Date = new Date()): number => {
|
||||
return normalize((getJulianDate(date) - 2451550.1) / LUNAR_MONTH);
|
||||
};
|
||||
|
||||
const getLunarAge = (date: Date = new Date()): number => {
|
||||
const percent: number = getLunarAgePercent(date);
|
||||
return percent * LUNAR_MONTH;
|
||||
};
|
||||
|
||||
export const getNextNewMoon = (date: Date = new Date()): Date => {
|
||||
const lunarAge: number = getLunarAge(date);
|
||||
return new Date(getLastNewMoon().getTime() + LUNAR_MONTH * 86400000);
|
||||
}
|
||||
|
||||
export const getLastNewMoon = (date: Date = new Date()):Date => {
|
||||
const lunarAge: number = getLunarAge(date);
|
||||
return new Date(date.getTime() - lunarAge * 86400000);
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
@tailwind utilities;
|
||||
|
||||
.fade {
|
||||
mask-image: linear-gradient(180deg,transparent, #000 3%, #000 97%, transparent);
|
||||
mask-image: linear-gradient(180deg, transparent, #000 3%, #000 97%, transparent);
|
||||
}
|
||||
|
||||
.tw-modal {
|
||||
@ -52,7 +52,7 @@
|
||||
|
||||
}
|
||||
|
||||
.custom-file-upload{
|
||||
.custom-file-upload {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -60,11 +60,11 @@ input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.custom-file-upload:hover .button{
|
||||
.custom-file-upload:hover .button {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.custom-file-upload .button{
|
||||
.custom-file-upload .button {
|
||||
transition: .5s ease;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
@ -74,4 +74,4 @@ input[type="file"] {
|
||||
|
||||
.tw-tab-content .container {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@ export {AppShell, Content, SideBar} from "./Components/AppShell"
|
||||
export {AuthProvider, useAuth, LoginPage, SignupPage, RequestPasswordPage, SetNewPasswordPage} from "./Components/Auth"
|
||||
export {UserSettings, ProfileSettings, OverlayProfile, OverlayProfileSettings, OverlayUserSettings} from './Components/Profile'
|
||||
export {Quests, Modal} from './Components/Gaming'
|
||||
export {TitleCard, CardPage, MapOverlayPage} from './Components/Templates'
|
||||
export {TitleCard, CardPage, MapOverlayPage, CircleLayout, MoonCalendar} from './Components/Templates'
|
||||
export {TextInput, TextAreaInput, SelectBox} from './Components/Input'
|
||||
|
||||
import "./index.css"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user