Add pill view

This commit is contained in:
2025-02-09 21:03:11 +02:00
parent 16a8403215
commit a93d7c8cac
9 changed files with 199 additions and 25 deletions

View File

@@ -9,11 +9,13 @@ export type Settings = {
viewAsTable: boolean;
useSavedBackgrounds: boolean;
unsplashQuery: string;
viewMode: 'tile' | 'pill';
};
export const settingsAtom = atomWithStorage<Settings>('vertex-settings', {
showLocations: false,
showImageDetails: false,
viewMode: 'tile',
viewAsTable: false,
useSavedBackgrounds: false,
unsplashQuery: '',

View File

@@ -20,7 +20,12 @@
.AppList {
display: flex;
gap: clamp(.5rem, 1vw, 2rem);
gap: clamp(0.5rem, 1vw, 1rem) clamp(0.5rem, 1vw, 2rem);
margin: 0 auto 2rem;
flex-wrap: wrap;
}
.AppList.Pill {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
}

View File

@@ -2,6 +2,9 @@ import { AppDefinition } from '~/types';
import { AppButton } from './app-button';
import style from './app-list.module.css';
import { AppPill } from './app-pill';
import { useAtomValue } from 'jotai';
import { settingsAtom } from '~/atoms';
type AppListProps = {
category: string;
@@ -9,17 +12,23 @@ type AppListProps = {
onReset?: () => void;
};
export function AppList({ category, apps, onReset }: AppListProps) {
const { viewMode } = useAtomValue(settingsAtom);
const Component = viewMode === 'tile' ? AppButton : AppPill;
return (
<div className={style.AppListWrap}>
<h2 className={style.Header}>
{category}
<span className={style.Close} onClick={onReset}>
×
</span>
</h2>
<div className={style.AppList}>
{category && (
<h2 className={style.Header}>
{category}
<span className={style.Close} onClick={onReset}>
×
</span>
</h2>
)}
<div className={`${style.AppList} ${viewMode === 'tile' ? style.Tile : style.Pill}`}>
{apps.length > 0
? apps.map((app) => <AppButton key={app.url} {...app} />)
? apps.map((app) => <Component key={app.url} {...app} />)
: 'No apps found'}
</div>
</div>

View File

@@ -0,0 +1,66 @@
.App {
position: relative;
overflow: hidden;
backdrop-filter: var(--bg-blur);
color: var(--color-text);
border-radius: var(--border-radius-default);
cursor: pointer;
background-color: var(--color-bg-dark);
transition: background-color var(--transition-duration-default) var(--transition-fn);
}
.App:hover,
.Box:focus-within {
background-color: var(--color-bg-darker);
}
.App > a {
display: flex;
align-items: center;
color: var(--color-text);
gap: 0.5rem;
padding: 0.8rem 0.7rem;
text-decoration: none;
&:hover {
text-decoration: none;
}
}
.Icon {
height: 30px;
aspect-ratio: 1/1;
padding: 4px;
}
.Icon.Bordered {
background-color: rgba(255, 255, 255, 0.9);
border-radius: 0.5rem;
}
.Url {
font-size: 0.8rem;
color: var(--color-secondary);
}
.Badge {
font-size: 0.7rem;
}
.Text {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.Name {
line-height: 1.3;
font-weight: bold;
}
.Index {
font-size: 1.5rem;
color: rgba(255, 255, 255, 0.3);
font-family: var(--font-accent);
}

View File

@@ -0,0 +1,44 @@
import { useAtomValue } from 'jotai';
import { getAppIcon } from '~/api/get-app-icon';
import { settingsAtom } from '~/atoms';
import { AppDefinition } from '~/types';
import style from './app-pill.module.css';
import { Anchor, Badge, Flex } from '@mantine/core';
export type AppPillProps = AppDefinition;
export function AppPill({
name,
url,
location,
icon,
icon_source,
icon_border,
shortcut,
}: AppPillProps) {
const { showLocations } = useAtomValue(settingsAtom);
return (
<div className={style.App}>
<Anchor href={url} target="_blank" rel="noopener noreferrer">
<img
className={`${style.Icon} ${icon_border ? style.Bordered : ''} `}
src={getAppIcon(icon, icon_source)}
alt={name}
/>
<div className={style.Text}>
<span className={style.Name}>{name}</span>
<span className={style.Url}>{url.split('://')[1].split('/')[0]}</span>
</div>
<Flex align="center" gap={4} justify="flex-end" style={{ marginLeft: 'auto' }}>
{shortcut && <div className={style.Index}>{shortcut}</div>}
{showLocations && location && (
<Badge size="xs" color="gray.8" className={style.Badge}>
{location}
</Badge>
)}
</Flex>
</Anchor>
</div>
);
}

View File

@@ -1,4 +1,16 @@
import { Badge, Button, Checkbox, Flex, Modal, TextInput, Tooltip } from '@mantine/core';
import {
Badge,
Button,
Checkbox,
Divider,
Flex,
Modal,
SegmentedControl,
Text,
TextInput,
Title,
Tooltip,
} from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { IconSettings, IconTrash } from '@tabler/icons-react';
import { useAtom, useAtomValue } from 'jotai';
@@ -20,15 +32,32 @@ export function Settings(props: SettingsProps) {
<IconSettings onClick={open} size={24} />
</Tooltip>
<Modal centered radius="lg" title="Settings" opened={isOpen} onClose={close}>
<Modal
centered
radius="lg"
title={<Title order={3}>Settings</Title>}
size="lg"
padding="lg"
opened={isOpen}
onClose={close}
>
<Flex direction="column" gap="1rem">
<Checkbox
label="Show image details"
checked={appSettings.showImageDetails}
onChange={(e) =>
setAppSettings((prev) => ({ ...prev, showImageDetails: e.target.checked }))
}
/>
<Title order={5}>App List</Title>
<Flex align="center" gap="sm">
<Text size="sm">App View</Text>
<SegmentedControl
fullWidth={false}
w="30%"
value={appSettings.viewMode}
onChange={(value) =>
setAppSettings((prev) => ({ ...prev, viewMode: value as 'tile' | 'pill' }))
}
data={[
{ label: 'Tiles', value: 'tile' },
{ label: 'Pills', value: 'pill' },
]}
/>
</Flex>
<Checkbox
label="Show locations"
checked={appSettings.showLocations}
@@ -36,10 +65,23 @@ export function Settings(props: SettingsProps) {
setAppSettings((prev) => ({ ...prev, showLocations: e.target.checked }))
}
/>
<Divider />
<Title order={5}>Backgrounds</Title>
<Checkbox
label="Show image details"
checked={appSettings.showImageDetails}
onChange={(e) =>
setAppSettings((prev) => ({ ...prev, showImageDetails: e.target.checked }))
}
/>
<Checkbox
label={
<>
Use favorited backgrounds <Badge size="sm">{favoriteBackgrounds.length}</Badge>
Use favorited backgrounds{' '}
<Badge size="sm" color="gray.7">
{favoriteBackgrounds.length}
</Badge>
</>
}
checked={appSettings.useSavedBackgrounds}
@@ -50,13 +92,15 @@ export function Settings(props: SettingsProps) {
<TextInput
label="Unsplash query"
labelProps={{ style: { marginBottom: '4px' } }}
value={appSettings.unsplashQuery}
onKeyDown={(e) => e.stopPropagation()}
onChange={(e) => setAppSettings((prev) => ({ ...prev, unsplashQuery: e.target.value }))}
/>
<Button
size="xs"
leftSection={<IconTrash />}
color="red.9"
leftSection={<IconTrash size={16} />}
onClick={() => {
localStorage.removeItem('start-page-background');
window.location.reload();

View File

@@ -36,13 +36,14 @@ export function WeatherWidget() {
<div className={`${style.WeatherLine} ${style.Delimiter}`}>
<i className={`wi wi-owm-${weather.id} ${style.WeatherIcon}`}></i>
<div className={style.FeelsLike}>
<span>{weather.feels_like}&deg;</span>
<span>{Math.round(weather.feels_like)}&deg;</span>
<span>{weather.main}</span>
</div>
</div>
<div className={style.WeatherLine}>
<IconChevronUp size={16} color="lightcoral" />
{weather.temp_min}&deg; <IconChevronDown size={16} color="lightgreen" /> {weather.temp_max}
{Math.round(weather.temp_min)}&deg; <IconChevronDown size={16} color="lightgreen" />{' '}
{Math.round(weather.temp_max)}
&deg;
<IconDroplet size={14} />
{weather.humidity}%<br />

View File

@@ -15,7 +15,7 @@
height: 100vh;
width: 100%;
overflow: scroll;
padding: 0 clamp(1rem, 10vw, 10rem);
padding: 0 clamp(0.6rem, 5vw, 10rem);
}
.Widgets {
@@ -23,7 +23,7 @@
flex-direction: column;
gap: 2rem;
width: 100%;
margin-top: calc(50vh - 240px);
margin-top: calc(20vh - 240px);
}
.MainWidgets {

View File

@@ -139,7 +139,10 @@ export function IndexPage() {
onReset={() => setSearchTerm('')}
/>
) : (
<WeatherWidget />
<>
<WeatherWidget />
<AppList category="" apps={apps} />
</>
)}
</>
)}