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
4 changes: 2 additions & 2 deletions apps/marketing/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"aliases": {
"components": "@/components",
"hooks": "@/hooks",
"lib": "@/lib",
"utils": "@bahar/web-ui/lib/utils",
"lib": "@bahar/design-system",
"utils": "@bahar/design-system",
"ui": "@bahar/web-ui/components"
}
}
3 changes: 2 additions & 1 deletion apps/marketing/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@bahar/web-ui/*": ["../../packages/web-ui/src/*"]
"@bahar/web-ui/*": ["../../packages/web-ui/src/*"],
"@bahar/design-system/*": ["../../packages/design-system/src/*"]
}
},
"include": ["src/**/*"],
Expand Down
44 changes: 44 additions & 0 deletions apps/web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,50 @@ import { cn } from "@bahar/web-ui/lib/utils";

See `packages/web-ui/README.md` for the full list of available components.

## Adding Color Themes

To add a new color theme:

1. **Add CSS variables** in `packages/design-system/colors.css`:

```css
[data-theme="mytheme"] {
--background: oklch(98% 0.01 180);
--primary: oklch(55% 0.18 180);
/* ... other light mode colors */
}

[data-theme="mytheme"].dark {
--background: oklch(15% 0.025 180);
--primary: oklch(65% 0.16 180);
/* ... other dark mode colors */
}
```

2. **Register the theme** in `src/atoms/theme.ts`:

```typescript
export enum ColorTheme {
DEFAULT = "default",
NORD = "nord",
MYTHEME = "mytheme",
}
```

3. **Add translation** in `src/components/ThemeMenu.tsx`:

```typescript
const ColorThemeLabel: FC<{ theme: ColorTheme }> = ({ theme }) => {
switch (theme) {
case ColorTheme.MYTHEME:
return <Trans>My Theme</Trans>;
// ... other cases
}
};
```

4. **Extract translations**: `pnpm run i18n:extract`

## Environment Variables

Required environment variables:
Expand Down
4 changes: 2 additions & 2 deletions apps/web/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"aliases": {
"components": "@/components",
"hooks": "@/hooks",
"lib": "@/lib",
"utils": "@bahar/web-ui/lib/utils",
"lib": "@bahar/design-system",
"utils": "@bahar/design-system",
"ui": "@bahar/web-ui/components"
}
}
5 changes: 5 additions & 0 deletions apps/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
<div id="root"></div>
<script>
"use strict";
const colorTheme = localStorage.getItem("color-theme");
if (colorTheme && colorTheme !== "default") {
document.documentElement.setAttribute("data-theme", colorTheme);
}

const theme = localStorage.getItem("theme");
const isSystemDark = window.matchMedia(
"(prefers-color-scheme: dark)"
Expand Down
37 changes: 37 additions & 0 deletions apps/web/src/atoms/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,40 @@ export const themeAtom = atomWithStorage<Theme>(
removeItem: (key) => localStorage.removeItem(key),
}
);

export enum ColorTheme {
DEFAULT = "default",
NORD = "nord",
TOKYO_NIGHT = "tokyo-night",
}

export const getAllColorThemes = () => Object.values(ColorTheme);

export const updateColorThemeInDOM = (theme: ColorTheme) => {
if (theme === ColorTheme.DEFAULT) {
document.documentElement.removeAttribute("data-theme");
} else {
document.documentElement.setAttribute("data-theme", theme);
}
};

export const colorThemeAtom = atomWithStorage<ColorTheme>(
"color-theme",
(localStorage.getItem("color-theme") as ColorTheme) ?? ColorTheme.DEFAULT,
{
getItem: (key) => {
const val =
(localStorage.getItem(key) as ColorTheme) ?? ColorTheme.DEFAULT;

updateColorThemeInDOM(val);

return val;
},
setItem: (key, val) => {
updateColorThemeInDOM(val);

localStorage.setItem(key, val);
},
removeItem: (key) => localStorage.removeItem(key),
}
);
2 changes: 1 addition & 1 deletion apps/web/src/components/DesktopNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const DesktopNavigation = () => {
const { logout } = useLogout();

return (
<aside className="fixed inset-y-0 z-10 hidden w-14 flex-col bg-gradient-to-b from-background to-background/95 backdrop-blur-sm sm:flex ltr:left-0 ltr:border-r rtl:right-0 rtl:border-l">
<aside className="fixed inset-y-0 z-10 hidden w-14 flex-col border-sidebar-border bg-sidebar sm:flex ltr:left-0 ltr:border-r rtl:right-0 rtl:border-l">
{/* Main nav buttons */}
<nav className="flex flex-col items-center gap-4 px-2 py-4">
<Link
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/LanguageMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const LanguageMenu = () => {
dynamicActivate(lng);
}}
>
<SelectTrigger className="w-max gap-x-2 dark:text-slate-50">
<SelectTrigger className="w-max gap-x-2">
<SelectValue />
</SelectTrigger>

Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/MobileHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const DraggableSheetContent: typeof SheetContent = React.forwardRef(
className={cn(
sheetVariantsNoSlideAnimations({ side }),
className,
"w-[70vw] border-border ltr:border-r rtl:border-l"
"w-[70vw] border-sidebar-border bg-sidebar ltr:border-r rtl:border-l"
)}
ref={ref}
{...props}
Expand Down Expand Up @@ -100,7 +100,7 @@ export const MobileHeader: FC<PropsWithChildren> = ({ children }) => {
const dir = useDir();

return (
<header className="sticky top-0 z-30 flex h-14 items-center gap-4 border-b bg-background px-4 sm:static sm:h-auto sm:border-0 sm:bg-transparent sm:px-6">
<header className="sticky top-0 z-30 flex h-14 items-center gap-4 border-sidebar-border border-b bg-sidebar px-4 sm:static sm:h-auto sm:border-0 sm:bg-transparent sm:px-6">
<Sheet open={isOpen}>
<SheetTrigger asChild>
<Button
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/OTPForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ function Slot(props: SlotProps) {
function FakeCaret() {
return (
<div className="pointer-events-none absolute inset-0 flex animate-caret-blink items-center justify-center">
<div className="h-8 w-px bg-white" />
<div className="h-8 w-px bg-foreground" />
</div>
);
}
Expand Down Expand Up @@ -130,7 +130,7 @@ export const OTPForm: FC<{
</Button>

<div className="flex flex-col items-center gap-y-4">
<h1 className="text-center font-bold text-gray-900 text-xl tracking-tight dark:text-white">
<h1 className="text-center font-bold text-foreground text-xl tracking-tight">
<Trans>Enter your 6-digit code from your email</Trans>
</h1>

Expand Down
60 changes: 57 additions & 3 deletions apps/web/src/components/ThemeMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,16 @@ import {
} from "@bahar/web-ui/components/select";
import { Trans } from "@lingui/react/macro";
import { useAtom } from "jotai";
import { Monitor, Moon, Sun } from "lucide-react";
import { Monitor, Moon, Palette, Sun } from "lucide-react";
import type { FC } from "react";
import { getAllThemes, Theme, themeAtom } from "@/atoms/theme";
import {
ColorTheme,
colorThemeAtom,
getAllColorThemes,
getAllThemes,
Theme,
themeAtom,
} from "@/atoms/theme";
import { useDir } from "@/hooks/useDir";

export const ThemeIcon: FC<{ theme: Theme }> = ({ theme }) => {
Expand Down Expand Up @@ -53,7 +60,7 @@ export const ThemeMenu = () => {
setTheme(theme);
}}
>
<SelectTrigger className="w-max gap-x-2 dark:text-slate-50">
<SelectTrigger className="w-max gap-x-2">
<SelectValue />
</SelectTrigger>

Expand All @@ -73,3 +80,50 @@ export const ThemeMenu = () => {
</Select>
);
};

const ColorThemeLabel: FC<{ theme: ColorTheme }> = ({ theme }) => {
switch (theme) {
case ColorTheme.NORD:
return <Trans>Nord</Trans>;

case ColorTheme.TOKYO_NIGHT:
return <Trans>Tokyo Night</Trans>;

case ColorTheme.DEFAULT:
default:
return <Trans>Default</Trans>;
}
};

export const ColorThemeMenu = () => {
const [colorTheme, setColorTheme] = useAtom(colorThemeAtom);
const dir = useDir();

return (
<Select
defaultValue={colorTheme}
dir={dir}
onValueChange={(theme: ColorTheme) => {
setColorTheme(theme);
}}
>
<SelectTrigger className="w-max gap-x-2">
<SelectValue />
</SelectTrigger>

<SelectContent>
<SelectGroup>
{getAllColorThemes().map((theme) => (
<SelectItem className="cursor-pointer" key={theme} value={theme}>
<div className="flex flex-row items-center gap-x-2">
<Palette size={16} />

<ColorThemeLabel theme={theme} />
</div>
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ export const FlashcardDrawer: FC<FlashcardDrawerProps> = ({
<TagBadgesList currentCard={currentCard} />

{/* Flashcard content area */}
<div className="relative rounded-2xl border border-border/30 bg-gradient-to-br from-muted/30 to-muted/10 p-4 sm:p-8">
<div className="relative rounded-2xl border border-border/50 bg-linear-to-br from-card to-card/50 p-4 shadow-lg sm:p-8">
{currentCard.direction === "reverse" ? (
<>
<ReverseQuestionSide currentCard={currentCard} />
Expand All @@ -407,7 +407,7 @@ export const FlashcardDrawer: FC<FlashcardDrawerProps> = ({
initial={{ opacity: 0, height: 0 }}
transition={{ duration: 0.3 }}
>
<div className="my-6 h-px bg-gradient-to-r from-transparent via-border to-transparent" />
<div className="my-6 h-px bg-linear-to-r from-transparent via-border to-transparent" />
<ReverseAnswerSide currentCard={currentCard} />
</motion.div>
)}
Expand All @@ -424,7 +424,7 @@ export const FlashcardDrawer: FC<FlashcardDrawerProps> = ({
initial={{ opacity: 0, height: 0 }}
transition={{ duration: 0.3 }}
>
<div className="my-6 h-px bg-gradient-to-r from-transparent via-border to-transparent" />
<div className="my-6 h-px bg-linear-to-r from-transparent via-border to-transparent" />
<AnswerSide currentCard={currentCard} />
</motion.div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const GradeFeedback: FC<{
[Rating.Again]: {
icon: RotateCcw,
color: "text-muted-foreground",
bgColor: "bg-muted/20",
bgColor: "bg-muted",
animation: {
initial: { scale: 0, rotate: 0 },
animate: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ import { Button } from "@bahar/web-ui/components/button";
import { Trans } from "@lingui/react/macro";
import { Brain, RotateCcw, ThumbsUp, Zap } from "lucide-react";
import { motion } from "motion/react";
import { type FC, type ReactNode, useMemo } from "react";
import { type FC, useMemo } from "react";
import { Rating } from "ts-fsrs";

type ReviewRating = Rating.Again | Rating.Hard | Rating.Good | Rating.Easy;

type GradeOptionConfig = {
label: ReactNode;
type GradeOptionsConfig = {
label: React.ReactNode;
borderStyles: string;
textStyles?: string;
glowColor: string;
icon: ReactNode;
icon: React.ReactNode;
};

type GradeOptionProps = {
Expand All @@ -28,8 +29,8 @@ export const GradeOption: FC<GradeOptionProps> = ({
intervalLabel,
disabled = false,
}) => {
const options = useMemo(() => {
const config: Record<ReviewRating, GradeOptionConfig> = {
const options: Record<ReviewRating, GradeOptionsConfig> = useMemo(() => {
const config = {
[Rating.Again]: {
label: <Trans>Again</Trans>,
borderStyles:
Expand Down Expand Up @@ -71,7 +72,7 @@ export const GradeOption: FC<GradeOptionProps> = ({
return config;
}, []);

const { label, icon, borderStyles, glowColor } = options[grade];
const { label, icon, borderStyles, glowColor, textStyles } = options[grade];

return (
<motion.div
Expand All @@ -85,9 +86,10 @@ export const GradeOption: FC<GradeOptionProps> = ({
<Button
className={cn(
"group h-auto w-full flex-col gap-1 border-2 px-2 py-3 shadow-md transition-all duration-300 sm:px-4",
"hover:shadow-lg",
"hover:text-foreground hover:shadow-lg",
borderStyles,
glowColor
glowColor,
textStyles
)}
disabled={disabled}
onClick={onClick}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,7 @@ export const TagBadgesList: FC<{
key={tag}
transition={{ delay: 0.15 + index * 0.05 }}
>
<Badge
className="w-max border-border/50 transition-colors hover:border-border"
variant="outline"
>
<Badge className="w-max border-border" variant="outline">
{tag}
</Badge>
</motion.div>
Expand Down
11 changes: 6 additions & 5 deletions apps/web/src/components/search/InfiniteScroll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -339,12 +339,13 @@ const WordCardContent: FC<WordCardContentProps> = memo(
<article
className={cn(
"group relative rounded-xl p-4",
"bg-gradient-to-br from-muted/30 to-muted/10",
"hover:from-muted/50 hover:to-muted/30",
"border border-transparent hover:border-border/50",
"bg-linear-to-br from-muted/40 to-muted/20 dark:from-muted/50 dark:to-muted/30",
"hover:from-muted/50 hover:to-muted/30 dark:hover:from-muted/60 dark:hover:to-muted/40",
"border border-border hover:border-border",
"transition-colors duration-200 ease-out",
"hover:shadow-black/5 hover:shadow-md",
isExpanded && "border-border/50 from-muted/50 to-muted/30"
isExpanded &&
"border-border/50 from-muted/50 to-muted/30 dark:from-muted/60 dark:to-muted/40"
)}
>
<div className="flex flex-col gap-y-2">
Expand Down Expand Up @@ -407,7 +408,7 @@ const WordCardContent: FC<WordCardContentProps> = memo(
</AnimatePresence>

{/* Subtle accent line */}
<div className="absolute top-1/2 h-0 w-1 -translate-y-1/2 rounded-full bg-gradient-to-b from-primary/60 to-primary/20 transition-all duration-300 ease-out group-hover:h-8 ltr:left-0 rtl:right-0" />
<div className="absolute top-1/2 h-0 w-1 -translate-y-1/2 rounded-full bg-linear-to-b from-primary/60 to-primary/20 transition-all duration-300 ease-out group-hover:h-8 ltr:left-0 rtl:right-0" />
</article>
);
}
Expand Down
Loading