mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 20:44:02 +00:00
refactor(miniapp): polish toolbar and control affordances
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import type { Locale } from '../../i18n'
|
import type { Locale } from '../../i18n'
|
||||||
|
import { GlobeIcon } from '../ui'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
subtitle: string
|
subtitle: string
|
||||||
@@ -17,13 +18,17 @@ export function TopBar(props: Props) {
|
|||||||
<h1>{props.title}</h1>
|
<h1>{props.title}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label class="locale-switch">
|
<div class="locale-switch locale-switch--compact">
|
||||||
<span>{props.languageLabel}</span>
|
<span class="locale-switch__label sr-only">{props.languageLabel}</span>
|
||||||
<div class="locale-switch__buttons">
|
<div class="locale-switch__buttons">
|
||||||
|
<span class="locale-switch__icon" aria-hidden="true">
|
||||||
|
<GlobeIcon />
|
||||||
|
</span>
|
||||||
<button
|
<button
|
||||||
classList={{ 'is-active': props.locale === 'en' }}
|
classList={{ 'is-active': props.locale === 'en' }}
|
||||||
type="button"
|
type="button"
|
||||||
disabled={props.saving}
|
disabled={props.saving}
|
||||||
|
aria-label={`${props.languageLabel}: English`}
|
||||||
onClick={() => props.onChange('en')}
|
onClick={() => props.onChange('en')}
|
||||||
>
|
>
|
||||||
EN
|
EN
|
||||||
@@ -32,12 +37,13 @@ export function TopBar(props: Props) {
|
|||||||
classList={{ 'is-active': props.locale === 'ru' }}
|
classList={{ 'is-active': props.locale === 'ru' }}
|
||||||
type="button"
|
type="button"
|
||||||
disabled={props.saving}
|
disabled={props.saving}
|
||||||
|
aria-label={`${props.languageLabel}: Russian`}
|
||||||
onClick={() => props.onChange('ru')}
|
onClick={() => props.onChange('ru')}
|
||||||
>
|
>
|
||||||
RU
|
RU
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
67
apps/miniapp/src/components/ui/icons.tsx
Normal file
67
apps/miniapp/src/components/ui/icons.tsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import type { JSX } from 'solid-js'
|
||||||
|
|
||||||
|
type IconProps = {
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function iconProps(props: IconProps): JSX.SvgSVGAttributes<SVGSVGElement> {
|
||||||
|
return {
|
||||||
|
viewBox: '0 0 24 24',
|
||||||
|
fill: 'none',
|
||||||
|
stroke: 'currentColor',
|
||||||
|
'stroke-width': 1.8,
|
||||||
|
'stroke-linecap': 'round',
|
||||||
|
'stroke-linejoin': 'round',
|
||||||
|
class: props.class ?? 'ui-icon',
|
||||||
|
'aria-hidden': 'true'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PencilIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg {...iconProps(props)}>
|
||||||
|
<path d="M12 20h9" />
|
||||||
|
<path d="m16.5 3.5 4 4L8 20l-5 1 1-5 12.5-12.5Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PlusIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg {...iconProps(props)}>
|
||||||
|
<path d="M12 5v14" />
|
||||||
|
<path d="M5 12h14" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SettingsIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg {...iconProps(props)}>
|
||||||
|
<circle cx="12" cy="12" r="3" />
|
||||||
|
<path d="M19.4 15a1 1 0 0 0 .2 1.1l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1 1 0 0 0-1.1-.2 1 1 0 0 0-.6.9V20a2 2 0 1 1-4 0v-.2a1 1 0 0 0-.7-.9 1 1 0 0 0-1 .2l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1 1 0 0 0 .2-1.1 1 1 0 0 0-.9-.6H4a2 2 0 1 1 0-4h.2a1 1 0 0 0 .9-.7 1 1 0 0 0-.2-1l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1 1 0 0 0 1.1.2 1 1 0 0 0 .6-.9V4a2 2 0 1 1 4 0v.2a1 1 0 0 0 .7.9 1 1 0 0 0 1-.2l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1 1 0 0 0-.2 1.1 1 1 0 0 0 .9.6H20a2 2 0 1 1 0 4h-.2a1 1 0 0 0-.9.7Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CalendarIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg {...iconProps(props)}>
|
||||||
|
<path d="M8 2v4" />
|
||||||
|
<path d="M16 2v4" />
|
||||||
|
<rect width="18" height="18" x="3" y="4" rx="2" />
|
||||||
|
<path d="M3 10h18" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GlobeIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg {...iconProps(props)}>
|
||||||
|
<circle cx="12" cy="12" r="9" />
|
||||||
|
<path d="M3 12h18" />
|
||||||
|
<path d="M12 3a15 15 0 0 1 0 18" />
|
||||||
|
<path d="M12 3a15 15 0 0 0 0 18" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -2,3 +2,4 @@ export * from './button'
|
|||||||
export * from './card'
|
export * from './card'
|
||||||
export * from './dialog'
|
export * from './dialog'
|
||||||
export * from './field'
|
export * from './field'
|
||||||
|
export * from './icons'
|
||||||
|
|||||||
@@ -114,10 +114,30 @@ button {
|
|||||||
font-size: 0.82rem;
|
font-size: 0.82rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.locale-switch--compact {
|
||||||
|
justify-items: end;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.locale-switch__buttons {
|
.locale-switch__buttons {
|
||||||
display: grid;
|
display: inline-grid;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-auto-flow: column;
|
||||||
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
|
padding: 4px;
|
||||||
|
border: 1px solid rgb(255 255 255 / 0.1);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgb(255 255 255 / 0.04);
|
||||||
|
box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.locale-switch__icon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
color: rgb(247 179 137 / 0.88);
|
||||||
}
|
}
|
||||||
|
|
||||||
.locale-switch__buttons button,
|
.locale-switch__buttons button,
|
||||||
@@ -134,7 +154,11 @@ button {
|
|||||||
|
|
||||||
.locale-switch__buttons button {
|
.locale-switch__buttons button {
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
padding: 10px 0;
|
min-width: 42px;
|
||||||
|
padding: 7px 10px;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.locale-switch__buttons button.is-active,
|
.locale-switch__buttons button.is-active,
|
||||||
@@ -257,10 +281,18 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ui-button--icon {
|
.ui-button--icon {
|
||||||
min-width: 44px;
|
min-width: 40px;
|
||||||
|
min-height: 40px;
|
||||||
|
border-radius: 12px;
|
||||||
padding-inline: 0;
|
padding-inline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ui-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-grid {
|
.nav-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
@@ -697,7 +729,9 @@ button {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
margin-top: 12px;
|
gap: 10px;
|
||||||
|
margin-top: 14px;
|
||||||
|
margin-bottom: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ledger-compact-card {
|
.ledger-compact-card {
|
||||||
@@ -995,10 +1029,19 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.locale-switch {
|
.locale-switch {
|
||||||
|
justify-items: stretch;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.locale-switch--compact {
|
||||||
|
justify-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.locale-switch__buttons {
|
||||||
|
justify-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-grid {
|
.nav-grid {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
import { For, Show } from 'solid-js'
|
import { For, Show } from 'solid-js'
|
||||||
|
|
||||||
import { Button, Field, IconButton, Modal } from '../components/ui'
|
import {
|
||||||
|
Button,
|
||||||
|
CalendarIcon,
|
||||||
|
Field,
|
||||||
|
IconButton,
|
||||||
|
Modal,
|
||||||
|
PencilIcon,
|
||||||
|
PlusIcon,
|
||||||
|
SettingsIcon
|
||||||
|
} from '../components/ui'
|
||||||
import type {
|
import type {
|
||||||
MiniAppAdminCycleState,
|
MiniAppAdminCycleState,
|
||||||
MiniAppAdminSettingsPayload,
|
MiniAppAdminSettingsPayload,
|
||||||
@@ -250,6 +259,7 @@ export function HouseScreen(props: Props) {
|
|||||||
</Show>
|
</Show>
|
||||||
<div class="panel-toolbar">
|
<div class="panel-toolbar">
|
||||||
<Button variant="secondary" onClick={props.onOpenCycleModal}>
|
<Button variant="secondary" onClick={props.onOpenCycleModal}>
|
||||||
|
<CalendarIcon />
|
||||||
{props.cycleState?.cycle
|
{props.cycleState?.cycle
|
||||||
? (props.copy.manageCycleAction ?? '')
|
? (props.copy.manageCycleAction ?? '')
|
||||||
: (props.copy.openCycleAction ?? '')}
|
: (props.copy.openCycleAction ?? '')}
|
||||||
@@ -289,6 +299,7 @@ export function HouseScreen(props: Props) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel-toolbar">
|
<div class="panel-toolbar">
|
||||||
<Button variant="secondary" onClick={props.onOpenBillingSettingsModal}>
|
<Button variant="secondary" onClick={props.onOpenBillingSettingsModal}>
|
||||||
|
<SettingsIcon />
|
||||||
{props.copy.manageSettingsAction ?? ''}
|
{props.copy.manageSettingsAction ?? ''}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -534,6 +545,7 @@ export function HouseScreen(props: Props) {
|
|||||||
<p>{props.copy.utilityBillsEditorBody ?? ''}</p>
|
<p>{props.copy.utilityBillsEditorBody ?? ''}</p>
|
||||||
<div class="panel-toolbar">
|
<div class="panel-toolbar">
|
||||||
<Button variant="secondary" onClick={props.onOpenAddUtilityBill}>
|
<Button variant="secondary" onClick={props.onOpenAddUtilityBill}>
|
||||||
|
<PlusIcon />
|
||||||
{props.copy.addUtilityBillAction ?? ''}
|
{props.copy.addUtilityBillAction ?? ''}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -559,7 +571,7 @@ export function HouseScreen(props: Props) {
|
|||||||
label={props.copy.editUtilityBillAction ?? ''}
|
label={props.copy.editUtilityBillAction ?? ''}
|
||||||
onClick={() => props.onOpenUtilityBillEditor(bill.id)}
|
onClick={() => props.onOpenUtilityBillEditor(bill.id)}
|
||||||
>
|
>
|
||||||
...
|
<PencilIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@@ -579,6 +591,7 @@ export function HouseScreen(props: Props) {
|
|||||||
<p>{props.copy.utilityCategoriesBody ?? ''}</p>
|
<p>{props.copy.utilityCategoriesBody ?? ''}</p>
|
||||||
<div class="panel-toolbar">
|
<div class="panel-toolbar">
|
||||||
<Button variant="secondary" onClick={() => props.onOpenCategoryEditor('__new__')}>
|
<Button variant="secondary" onClick={() => props.onOpenCategoryEditor('__new__')}>
|
||||||
|
<PlusIcon />
|
||||||
{props.copy.addCategoryAction ?? ''}
|
{props.copy.addCategoryAction ?? ''}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -603,7 +616,7 @@ export function HouseScreen(props: Props) {
|
|||||||
label={props.copy.editCategoryAction ?? ''}
|
label={props.copy.editCategoryAction ?? ''}
|
||||||
onClick={() => props.onOpenCategoryEditor(category.slug)}
|
onClick={() => props.onOpenCategoryEditor(category.slug)}
|
||||||
>
|
>
|
||||||
...
|
<PencilIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@@ -898,7 +911,7 @@ export function HouseScreen(props: Props) {
|
|||||||
label={props.copy.editMemberAction ?? ''}
|
label={props.copy.editMemberAction ?? ''}
|
||||||
onClick={() => props.onOpenMemberEditor(member.id)}
|
onClick={() => props.onOpenMemberEditor(member.id)}
|
||||||
>
|
>
|
||||||
...
|
<PencilIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { For, Show } from 'solid-js'
|
import { For, Show } from 'solid-js'
|
||||||
|
|
||||||
import { Button, Field, IconButton, Modal } from '../components/ui'
|
import { Button, Field, IconButton, Modal, PencilIcon } from '../components/ui'
|
||||||
import type { MiniAppAdminSettingsPayload, MiniAppDashboard } from '../miniapp-api'
|
import type { MiniAppAdminSettingsPayload, MiniAppDashboard } from '../miniapp-api'
|
||||||
|
|
||||||
type PurchaseDraft = {
|
type PurchaseDraft = {
|
||||||
@@ -168,7 +168,7 @@ export function LedgerScreen(props: Props) {
|
|||||||
label={props.copy.editEntryAction ?? ''}
|
label={props.copy.editEntryAction ?? ''}
|
||||||
onClick={() => props.onOpenPurchaseEditor(entry.id)}
|
onClick={() => props.onOpenPurchaseEditor(entry.id)}
|
||||||
>
|
>
|
||||||
...
|
<PencilIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
@@ -420,7 +420,7 @@ export function LedgerScreen(props: Props) {
|
|||||||
label={props.copy.editEntryAction ?? ''}
|
label={props.copy.editEntryAction ?? ''}
|
||||||
onClick={() => props.onOpenPaymentEditor(entry.id)}
|
onClick={() => props.onOpenPaymentEditor(entry.id)}
|
||||||
>
|
>
|
||||||
...
|
<PencilIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
Reference in New Issue
Block a user