Skip to content
Merged
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
2 changes: 2 additions & 0 deletions src/components/CalendarHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import styles from './CalendarHeader.module.css';
registerLocale('he', he);
import { CalendarListEntry } from '@/lib/google-calendar';
import CalendarFilter from './CalendarFilter';
import DensityControl from './DensityControl';
import { signOut } from 'next-auth/react';

interface CalendarHeaderProps {
Expand Down Expand Up @@ -280,6 +281,7 @@ export default function CalendarHeader({
>
<span aria-hidden="true">◫</span> מפרידים
</button>
<DensityControl />
</div>
<button onClick={onToggleTheme} className={styles.themeToggle} title={`עבור למצב ${theme === 'dark' ? 'בהיר' : 'כהה'}`}>
{theme === 'dark' ? '☀️' : '🌙'}
Expand Down
128 changes: 128 additions & 0 deletions src/components/DensityControl.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
.container {
position: relative;
display: inline-flex;
}

/* Trigger styled to match the .viewToggle buttons in the header's "תצוגה" group. */
.trigger {
display: inline-flex;
align-items: center;
gap: 0.3rem;
padding: 0.3rem 0.5rem;
font-size: 0.8rem;
font-weight: 500;
background: transparent;
border: 1px solid transparent;
border-radius: 6px;
color: var(--text-primary);
cursor: pointer;
white-space: nowrap;
transition: all 0.15s;
}

.trigger:hover {
background-color: var(--btn-hover);
}

.popover {
position: absolute;
top: 100%;
left: 0;
margin-top: 8px;
width: 260px;
background-color: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 10px;
padding: 12px;
box-shadow: 0 10px 24px -6px rgba(0, 0, 0, 0.4);
z-index: 100;
display: flex;
flex-direction: column;
gap: 12px;
}

.heading {
font-size: 0.8rem;
font-weight: 700;
color: var(--text-secondary);
}

/* Segmented preset control */
.presets {
display: flex;
gap: 4px;
background: var(--btn-bg);
border-radius: 8px;
padding: 3px;
}

.preset {
flex: 1;
padding: 6px 0;
font-size: 0.8rem;
font-weight: 600;
border: none;
border-radius: 6px;
background: transparent;
color: var(--text-secondary);
cursor: pointer;
transition: all 0.15s;
}

.preset:hover {
color: var(--text-primary);
}

.presetActive {
background: var(--bg-card);
color: var(--text-accent);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
}

.advanced {
display: flex;
flex-direction: column;
gap: 10px;
}

.sliderRow {
display: grid;
grid-template-columns: 5.5rem 1fr 2.5rem;
align-items: center;
gap: 8px;
font-size: 0.78rem;
color: var(--text-primary);
}

.sliderLabel {
white-space: nowrap;
}

.range {
width: 100%;
accent-color: var(--text-accent);
cursor: pointer;
}

.sliderValue {
text-align: end;
color: var(--text-muted);
font-variant-numeric: tabular-nums;
}

.reset {
align-self: flex-start;
padding: 4px 8px;
font-size: 0.75rem;
background: transparent;
border: 1px solid var(--border-color);
border-radius: 6px;
color: var(--text-secondary);
cursor: pointer;
transition: all 0.15s;
}

.reset:hover {
background-color: var(--btn-hover);
color: var(--text-primary);
}
142 changes: 142 additions & 0 deletions src/components/DensityControl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
'use client';

import React, { useState, useEffect, useRef } from 'react';
import styles from './DensityControl.module.css';
import {
DensitySettings,
DensityPresetName,
DENSITY_PRESETS,
DEFAULT_DENSITY,
DENSITY_BOUNDS,
densityToCssVars,
matchPreset,
parseDensity,
clampDensity,
} from '@/lib/density';

const STORAGE_KEY = 'densitySettings';

const PRESET_LABELS: Record<DensityPresetName, string> = {
compact: 'דחוס',
comfortable: 'רגיל',
spacious: 'מרווח',
};

function applyVars(s: DensitySettings) {
const vars = densityToCssVars(s);
for (const [k, v] of Object.entries(vars)) {
document.documentElement.style.setProperty(k, v);
}
}

interface SliderRowProps {
label: string;
value: number;
bounds: { min: number; max: number };
onChange: (v: number) => void;
}

function SliderRow({ label, value, bounds, onChange }: SliderRowProps) {
return (
<label className={styles.sliderRow}>
<span className={styles.sliderLabel}>{label}</span>
<input
type="range"
min={bounds.min}
max={bounds.max}
value={value}
onChange={e => onChange(Number(e.target.value))}
className={styles.range}
/>
<span className={styles.sliderValue}>{value}px</span>
</label>
);
}

export default function DensityControl() {
const [settings, setSettings] = useState<DensitySettings>(DEFAULT_DENSITY);
const [open, setOpen] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);

// Load persisted settings on mount and apply them.
useEffect(() => {
const saved = parseDensity(localStorage.getItem(STORAGE_KEY));
if (saved) {
setSettings(saved);
applyVars(saved);
}
}, []);

// Close the popover on outside click.
useEffect(() => {
const onDown = (e: MouseEvent) => {
if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
setOpen(false);
}
};
if (open) document.addEventListener('mousedown', onDown);
return () => document.removeEventListener('mousedown', onDown);
}, [open]);

const update = (next: DensitySettings) => {
const clamped = clampDensity(next);
setSettings(clamped);
applyVars(clamped);
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(clamped));
} catch {
/* ignore quota / private-mode errors */
}
};

const active = matchPreset(settings);

return (
<div className={styles.container} ref={containerRef}>
<button
type="button"
className={styles.trigger}
onClick={() => setOpen(o => !o)}
title="גודל וצפיפות התצוגה"
aria-haspopup="dialog"
aria-expanded={open}
>
<span aria-hidden="true">↕</span> גודל
</button>

{open && (
<div className={styles.popover} dir="rtl" role="dialog" aria-label="גודל וצפיפות">
<div className={styles.heading}>גודל וצפיפות</div>

<div className={styles.presets}>
{(Object.keys(DENSITY_PRESETS) as DensityPresetName[]).map(name => (
<button
key={name}
type="button"
className={`${styles.preset} ${active === name ? styles.presetActive : ''}`}
onClick={() => update(DENSITY_PRESETS[name])}
>
{PRESET_LABELS[name]}
</button>
))}
</div>

<div className={styles.advanced}>
<SliderRow label="גובה שורה" value={settings.cellMinH} bounds={DENSITY_BOUNDS.cellMinH}
onChange={v => update({ ...settings, cellMinH: v })} />
<SliderRow label="גובה אירוע" value={settings.barH} bounds={DENSITY_BOUNDS.barH}
onChange={v => update({ ...settings, barH: v })} />
<SliderRow label="פונט אירוע" value={settings.barFont} bounds={DENSITY_BOUNDS.barFont}
onChange={v => update({ ...settings, barFont: v })} />
<SliderRow label="רוחב עמודה" value={settings.colMinW} bounds={DENSITY_BOUNDS.colMinW}
onChange={v => update({ ...settings, colMinW: v })} />
</div>

<button type="button" className={styles.reset} onClick={() => update(DEFAULT_DENSITY)}>
איפוס לרגיל
</button>
</div>
)}
</div>
);
}
12 changes: 6 additions & 6 deletions src/components/LinearCalendar.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
.mainGrid {
display: grid;
/* 1 Month Label Col + 37 Cols */
grid-template-columns: 100px repeat(37, minmax(40px, 1fr));
grid-template-columns: 100px repeat(37, minmax(var(--col-min-w, 40px), 1fr));
gap: 24px 4px;
min-width: 1500px;
/* Force scroll for large grids */
Expand Down Expand Up @@ -67,7 +67,7 @@
.dayCell {
border: 1px solid var(--border-color);
border-radius: 6px;
min-height: 80px;
min-height: var(--cell-min-h, 80px);
position: relative;
background-color: var(--bg-card);
transition: all 0.2s;
Expand Down Expand Up @@ -133,7 +133,7 @@
}

.dayNumber {
font-size: 0.85rem;
font-size: var(--day-num-font, 0.85rem);
font-weight: 700;
color: var(--cal-text);
line-height: 1;
Expand Down Expand Up @@ -210,12 +210,12 @@
}

.emptyTrack {
height: 18px;
height: var(--bar-h, 18px);
width: 100%;
}

.multiDayBar {
height: 18px;
height: var(--bar-h, 18px);
margin-inline: -3px;
background-color: var(--event-bar-bg, #3d7eff);
position: relative;
Expand Down Expand Up @@ -251,7 +251,7 @@

.barLabel {
padding: 0 4px;
font-size: clamp(8px, 15cqi, 11px);
font-size: clamp(8px, 15cqi, var(--bar-font, 11px));
font-weight: 500;
white-space: nowrap;
color: inherit;
Expand Down
2 changes: 1 addition & 1 deletion src/components/LinearCalendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -901,7 +901,7 @@ export default function LinearCalendar({ events: initialEventsProp, startDate, e
<div
key={trackIdx}
className={styles.multiDayBar}
style={{ visibility: 'hidden', height: '18px' }} // Ensure height matches
style={{ visibility: 'hidden', height: 'var(--bar-h, 18px)' }} // Ensure height matches
/>
);
}
Expand Down
Loading
Loading