mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-11 09:40:28 +03:00
Add settings to enable 24-hour time format and customizable date format (#2347)
* Add setting to enable 24-hour time format * added hour24Clock to TimeProps * Add incomplete dateFormatString setting * Move 24-hour toggle to Appearance * Add "Date & Time" subheading, cleanup after merge * Add setting for date formatting * Fix minor formatting and naming issues * Document functions * adress most comments * add hint for date formatting * add support for 24hr time to TimePicker * prevent overflow on small displays
This commit is contained in:
parent
67b05eeb09
commit
9183fd66b2
17 changed files with 691 additions and 82 deletions
|
|
@ -5,19 +5,35 @@ import { timeDayMonYear, timeHourMinute, today, yesterday } from '../../utils/ti
|
|||
export type TimeProps = {
|
||||
compact?: boolean;
|
||||
ts: number;
|
||||
hour24Clock: boolean;
|
||||
dateFormatString: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a formatted timestamp, supporting compact and full display modes.
|
||||
*
|
||||
* Displays the time in hour:minute format if the message is from today, yesterday, or if `compact` is true.
|
||||
* For older messages, it shows the date and time.
|
||||
*
|
||||
* @param {number} ts - The timestamp to display.
|
||||
* @param {boolean} [compact=false] - If true, always show only the time.
|
||||
* @param {boolean} hour24Clock - Whether to use 24-hour time format.
|
||||
* @param {string} dateFormatString - Format string for the date part.
|
||||
* @returns {React.ReactElement} A <Text as="time"> element with the formatted date/time.
|
||||
*/
|
||||
export const Time = as<'span', TimeProps & ComponentProps<typeof Text>>(
|
||||
({ compact, ts, ...props }, ref) => {
|
||||
({ compact, hour24Clock, dateFormatString, ts, ...props }, ref) => {
|
||||
const formattedTime = timeHourMinute(ts, hour24Clock);
|
||||
|
||||
let time = '';
|
||||
if (compact) {
|
||||
time = timeHourMinute(ts);
|
||||
time = formattedTime;
|
||||
} else if (today(ts)) {
|
||||
time = timeHourMinute(ts);
|
||||
time = formattedTime;
|
||||
} else if (yesterday(ts)) {
|
||||
time = `Yesterday ${timeHourMinute(ts)}`;
|
||||
time = `Yesterday ${formattedTime}`;
|
||||
} else {
|
||||
time = `${timeDayMonYear(ts)} ${timeHourMinute(ts)}`;
|
||||
time = `${timeDayMonYear(ts, dateFormatString)} ${formattedTime}`;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import { nameInitials } from '../../utils/common';
|
|||
import { useRoomAvatar, useRoomName, useRoomTopic } from '../../hooks/useRoomMeta';
|
||||
import { mDirectAtom } from '../../state/mDirectList';
|
||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||
import { useSetting } from '../../state/hooks/settings';
|
||||
import { settingsAtom } from '../../state/settings';
|
||||
|
||||
export type RoomIntroProps = {
|
||||
room: Room;
|
||||
|
|
@ -43,6 +45,8 @@ export const RoomIntro = as<'div', RoomIntroProps>(({ room, ...props }, ref) =>
|
|||
useCallback(async (roomId: string) => mx.joinRoom(roomId), [mx])
|
||||
);
|
||||
|
||||
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
||||
|
||||
return (
|
||||
<Box direction="Column" grow="Yes" gap="500" {...props} ref={ref}>
|
||||
<Box>
|
||||
|
|
@ -67,7 +71,7 @@ export const RoomIntro = as<'div', RoomIntroProps>(({ room, ...props }, ref) =>
|
|||
<Text size="T200" priority="300">
|
||||
{'Created by '}
|
||||
<b>@{creatorName}</b>
|
||||
{` on ${timeDayMonthYear(ts)} ${timeHourMinute(ts)}`}
|
||||
{` on ${timeDayMonthYear(ts)} ${timeHourMinute(ts, hour24Clock)}`}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import dayjs from 'dayjs';
|
|||
import * as css from './styles.css';
|
||||
import { PickerColumn } from './PickerColumn';
|
||||
import { hour12to24, hour24to12, hoursToMs, inSameDay, minutesToMs } from '../../utils/time';
|
||||
import { useSetting } from '../../state/hooks/settings';
|
||||
import { settingsAtom } from '../../state/settings';
|
||||
|
||||
type TimePickerProps = {
|
||||
min: number;
|
||||
|
|
@ -13,9 +15,11 @@ type TimePickerProps = {
|
|||
};
|
||||
export const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
|
||||
({ min, max, value, onChange }, ref) => {
|
||||
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
||||
|
||||
const hour24 = dayjs(value).hour();
|
||||
|
||||
const selectedHour = hour24to12(hour24);
|
||||
const selectedHour = hour24Clock ? hour24 : hour24to12(hour24);
|
||||
const selectedMinute = dayjs(value).minute();
|
||||
const selectedPM = hour24 >= 12;
|
||||
|
||||
|
|
@ -24,7 +28,7 @@ export const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
|
|||
};
|
||||
|
||||
const handleHour = (hour: number) => {
|
||||
const seconds = hoursToMs(hour12to24(hour, selectedPM));
|
||||
const seconds = hoursToMs(hour24Clock ? hour : hour12to24(hour, selectedPM));
|
||||
const lastSeconds = hoursToMs(hour24);
|
||||
const newValue = value + (seconds - lastSeconds);
|
||||
handleSubmit(newValue);
|
||||
|
|
@ -59,28 +63,43 @@ export const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
|
|||
<Menu className={css.PickerMenu} ref={ref}>
|
||||
<Box direction="Row" gap="200" className={css.PickerContainer}>
|
||||
<PickerColumn title="Hour">
|
||||
{Array.from(Array(12).keys())
|
||||
.map((i) => {
|
||||
if (i === 0) return 12;
|
||||
return i;
|
||||
})
|
||||
.map((hour) => (
|
||||
<Chip
|
||||
key={hour}
|
||||
size="500"
|
||||
variant={hour === selectedHour ? 'Primary' : 'Background'}
|
||||
fill="None"
|
||||
radii="300"
|
||||
aria-selected={hour === selectedHour}
|
||||
onClick={() => handleHour(hour)}
|
||||
disabled={
|
||||
(minDay && hour12to24(hour, selectedPM) < minHour24) ||
|
||||
(maxDay && hour12to24(hour, selectedPM) > maxHour24)
|
||||
}
|
||||
>
|
||||
<Text size="T300">{hour < 10 ? `0${hour}` : hour}</Text>
|
||||
</Chip>
|
||||
))}
|
||||
{hour24Clock
|
||||
? Array.from(Array(24).keys()).map((hour) => (
|
||||
<Chip
|
||||
key={hour}
|
||||
size="500"
|
||||
variant={hour === selectedHour ? 'Primary' : 'Background'}
|
||||
fill="None"
|
||||
radii="300"
|
||||
aria-selected={hour === selectedHour}
|
||||
onClick={() => handleHour(hour)}
|
||||
disabled={(minDay && hour < minHour24) || (maxDay && hour > maxHour24)}
|
||||
>
|
||||
<Text size="T300">{hour < 10 ? `0${hour}` : hour}</Text>
|
||||
</Chip>
|
||||
))
|
||||
: Array.from(Array(12).keys())
|
||||
.map((i) => {
|
||||
if (i === 0) return 12;
|
||||
return i;
|
||||
})
|
||||
.map((hour) => (
|
||||
<Chip
|
||||
key={hour}
|
||||
size="500"
|
||||
variant={hour === selectedHour ? 'Primary' : 'Background'}
|
||||
fill="None"
|
||||
radii="300"
|
||||
aria-selected={hour === selectedHour}
|
||||
onClick={() => handleHour(hour)}
|
||||
disabled={
|
||||
(minDay && hour12to24(hour, selectedPM) < minHour24) ||
|
||||
(maxDay && hour12to24(hour, selectedPM) > maxHour24)
|
||||
}
|
||||
>
|
||||
<Text size="T300">{hour < 10 ? `0${hour}` : hour}</Text>
|
||||
</Chip>
|
||||
))}
|
||||
</PickerColumn>
|
||||
<PickerColumn title="Minutes">
|
||||
{Array.from(Array(60).keys()).map((minute) => (
|
||||
|
|
@ -101,30 +120,32 @@ export const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
|
|||
</Chip>
|
||||
))}
|
||||
</PickerColumn>
|
||||
<PickerColumn title="Period">
|
||||
<Chip
|
||||
size="500"
|
||||
variant={!selectedPM ? 'Primary' : 'SurfaceVariant'}
|
||||
fill="None"
|
||||
radii="300"
|
||||
aria-selected={!selectedPM}
|
||||
onClick={() => handlePeriod(false)}
|
||||
disabled={minDay && minPM}
|
||||
>
|
||||
<Text size="T300">AM</Text>
|
||||
</Chip>
|
||||
<Chip
|
||||
size="500"
|
||||
variant={selectedPM ? 'Primary' : 'SurfaceVariant'}
|
||||
fill="None"
|
||||
radii="300"
|
||||
aria-selected={selectedPM}
|
||||
onClick={() => handlePeriod(true)}
|
||||
disabled={maxDay && !maxPM}
|
||||
>
|
||||
<Text size="T300">PM</Text>
|
||||
</Chip>
|
||||
</PickerColumn>
|
||||
{!hour24Clock && (
|
||||
<PickerColumn title="Period">
|
||||
<Chip
|
||||
size="500"
|
||||
variant={!selectedPM ? 'Primary' : 'SurfaceVariant'}
|
||||
fill="None"
|
||||
radii="300"
|
||||
aria-selected={!selectedPM}
|
||||
onClick={() => handlePeriod(false)}
|
||||
disabled={minDay && minPM}
|
||||
>
|
||||
<Text size="T300">AM</Text>
|
||||
</Chip>
|
||||
<Chip
|
||||
size="500"
|
||||
variant={selectedPM ? 'Primary' : 'SurfaceVariant'}
|
||||
fill="None"
|
||||
radii="300"
|
||||
aria-selected={selectedPM}
|
||||
onClick={() => handlePeriod(true)}
|
||||
disabled={maxDay && !maxPM}
|
||||
>
|
||||
<Text size="T300">PM</Text>
|
||||
</Chip>
|
||||
</PickerColumn>
|
||||
)}
|
||||
</Box>
|
||||
</Menu>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue