Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions app/components/shared/top_bar.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
// contains the logo, the name and the "Log in" button.
import { Button } from '~/shadcn/components/ui/button';
import { Item, ItemActions, ItemContent, ItemMedia, ItemTitle } from '~/shadcn/components/ui/item';
import { Popover, PopoverContent, PopoverTrigger } from '~/shadcn/components/ui/popover';
import { Slider } from '~/shadcn/components/ui/slider';
import { useKeycloak } from '@react-keycloak/web';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { appRoutes } from '~/lib/app_routes';
import { useState, useEffect } from 'react';
import { Contrast } from 'lucide-react';

export function TopBar({ isLoginPage }: { isLoginPage: boolean }) {
const { keycloak, initialized } = useKeycloak();
const { t, i18n } = useTranslation();
const navigate = useNavigate();
const itemClassName = isLoginPage ? 'justify-center' : 'justify-between';

const [contrast, setContrast] = useState(() => {
return Number(localStorage.getItem('jucaneat-contrast') ?? 1);
});

useEffect(() => {
const root = document.documentElement;
root.style.filter = contrast > 1 ? `contrast(${contrast})` : '';
root.classList.toggle('high-contrast', contrast > 1);
localStorage.setItem('jucaneat-contrast', String(contrast));
}, [contrast]);

const handleLogin = () => {
if (!initialized) return;

Expand Down Expand Up @@ -88,6 +103,51 @@ export function TopBar({ isLoginPage }: { isLoginPage: boolean }) {
>
PL
</Button>
<Popover>
<PopoverTrigger asChild>
<Button
size="xsm"
variant={contrast > 1 ? 'default' : 'outline'}
className={
contrast > 1
? 'border-gray-200 shadow-sm bg-black text-white hover:bg-black/90 hover:text-white dark:border-zinc-200 dark:bg-white dark:text-black dark:hover:bg-zinc-100'
: 'border-gray-200 shadow-sm bg-white text-black hover:bg-gray-50 dark:border-zinc-700 dark:bg-zinc-900 dark:text-white dark:hover:bg-zinc-800'
}
aria-label={t('topBar.highContrast')}
>
<Contrast size={14} />
</Button>
</PopoverTrigger>
<PopoverContent
className="w-56"
align="end"
>
<div className="flex flex-col gap-3">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">{t('topBar.highContrast')}</span>
{contrast > 1 && (
<button
onClick={() => setContrast(1)}
className="text-xs text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100"
>
{t('topBar.contrastReset')}
</button>
)}
</div>
<Slider
min={1}
max={1.2}
step={0.05}
value={[contrast]}
onValueChange={([val]) => setContrast(val)}
/>
<div className="flex justify-between text-xs text-gray-500 dark:text-gray-400">
<span>{t('topBar.contrastNormal')}</span>
<span>{t('topBar.contrastHigh')}</span>
</div>
</div>
</PopoverContent>
</Popover>
</div>
)}

Expand Down
8 changes: 8 additions & 0 deletions app/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ const resources = {
login: 'Login',
logout: 'Logout',
languageLabel: 'Language',
highContrast: 'Contrast',
contrastReset: 'Reset',
contrastNormal: 'Normal',
contrastHigh: 'High',
},
common: {
loading: 'Loading...',
Expand Down Expand Up @@ -264,6 +268,10 @@ const resources = {
login: 'Zaloguj',
logout: 'Wyloguj',
languageLabel: 'JΔ™zyk',
highContrast: 'Kontrast',
contrastReset: 'Resetuj',
contrastNormal: 'Normalny',
contrastHigh: 'Wysoki',
},
common: {
loading: 'Ładowanie...',
Expand Down
95 changes: 95 additions & 0 deletions app/shadcn/components/ui/popover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
'use client';

import * as React from 'react';
import { Popover as PopoverPrimitive } from 'radix-ui';

import { cn } from '~/shadcn/lib/utils';

function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
return (
<PopoverPrimitive.Root
data-slot="popover"
{...props}
/>
);
}

function PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
return (
<PopoverPrimitive.Trigger
data-slot="popover-trigger"
{...props}
/>
);
}

function PopoverContent({
className,
align = 'center',
sideOffset = 4,
...props
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
return (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
data-slot="popover-content"
align={align}
sideOffset={sideOffset}
className={cn(
'z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border border-gray-200 bg-white p-4 text-gray-950 shadow-md outline-hidden data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 dark:border-gray-800 dark:bg-gray-950 dark:text-gray-50',
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
);
}

function PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
return (
<PopoverPrimitive.Anchor
data-slot="popover-anchor"
{...props}
/>
);
}

function PopoverHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="popover-header"
className={cn('flex flex-col gap-1 text-sm', className)}
{...props}
/>
);
}

function PopoverTitle({ className, ...props }: React.ComponentProps<'h2'>) {
return (
<div
data-slot="popover-title"
className={cn('font-medium', className)}
{...props}
/>
);
}

function PopoverDescription({ className, ...props }: React.ComponentProps<'p'>) {
return (
<p
data-slot="popover-description"
className={cn('text-gray-500 dark:text-gray-400', className)}
{...props}
/>
);
}

export {
Popover,
PopoverTrigger,
PopoverContent,
PopoverAnchor,
PopoverHeader,
PopoverTitle,
PopoverDescription,
};
56 changes: 56 additions & 0 deletions app/shadcn/components/ui/slider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as React from 'react';
import { Slider as SliderPrimitive } from 'radix-ui';

import { cn } from '~/shadcn/lib/utils';

function Slider({
className,
defaultValue,
value,
min = 0,
max = 100,
...props
}: React.ComponentProps<typeof SliderPrimitive.Root>) {
const _values = React.useMemo(
() => (Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max]),
[value, defaultValue, min, max]
);

return (
<SliderPrimitive.Root
data-slot="slider"
defaultValue={defaultValue}
value={value}
min={min}
max={max}
className={cn(
'relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col',
className
)}
{...props}
>
<SliderPrimitive.Track
data-slot="slider-track"
className={cn(
'relative grow overflow-hidden rounded-full bg-gray-100 data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5 dark:bg-gray-800'
)}
>
<SliderPrimitive.Range
data-slot="slider-range"
className={cn(
'absolute bg-gray-900 data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full dark:bg-gray-50'
)}
/>
</SliderPrimitive.Track>
{Array.from({ length: _values.length }, (_, index) => (
<SliderPrimitive.Thumb
data-slot="slider-thumb"
key={index}
className="block size-4 shrink-0 rounded-full border border-gray-200 border-gray-900 bg-white shadow-sm ring-gray-950/50 transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50 dark:border-gray-800 dark:border-gray-50 dark:ring-gray-300/50"
/>
))}
</SliderPrimitive.Root>
);
}

export { Slider };
6 changes: 6 additions & 0 deletions app/tailwind_styles.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
@import 'tailwindcss';
@import 'tw-animate-css';

@media (prefers-color-scheme: light) {
html.high-contrast * {
border-color: rgb(0 0 0 / 0.15) !important;
}
}

@theme {
--breakpoint-3xs: 16rem; /* 256px */
--breakpoint-2xs: 18rem; /* 288px */
Expand Down
Loading
Loading