Commit 35928ce5 authored by Johnny's avatar Johnny

chore: unify colors

parent 2e474d37
# Color System Guide
This document explains the color system used in the Memos application, built with OKLCH color space for better perceptual uniformity and accessibility.
## Overview
The color system supports both light and dark themes automatically through CSS custom properties. All colors are defined using OKLCH (Oklab LCH) color space, which provides better perceptual uniformity than traditional RGB/HSL.
## Color Categories
### 🎨 Primary Brand Colors
| Variable | Light Theme | Dark Theme | Usage |
| ---------------------- | ------------- | --------------- | ------------------------------ |
| `--primary` | Golden yellow | Brighter golden | Main brand color, primary CTAs |
| `--primary-foreground` | White | White | Text on primary backgrounds |
**When to use:**
- Call-to-action buttons
- Active navigation items
- Important links and highlights
- Brand elements
```css
/* Example usage */
.cta-button {
background: var(--primary);
color: var(--primary-foreground);
}
```
### 🔘 Secondary Colors
| Variable | Light Theme | Dark Theme | Usage |
| ------------------------ | ----------- | --------------- | ----------------------------- |
| `--secondary` | Light gray | Very light gray | Supporting actions |
| `--secondary-foreground` | Dark gray | Dark gray | Text on secondary backgrounds |
**When to use:**
- Secondary buttons
- Less important actions
- Alternative navigation items
- Subtle highlights
### 📄 Background & Surface Colors
| Variable | Light Theme | Dark Theme | Usage |
| ---------------------- | ----------- | ----------- | --------------------------- |
| `--background` | Near white | Dark gray | Main page background |
| `--card` | Near white | Dark gray | Card/container backgrounds |
| `--card-foreground` | Very dark | Near white | Text on card backgrounds |
| `--popover` | Pure white | Darker gray | Overlay backgrounds |
| `--popover-foreground` | Dark gray | Light gray | Text on overlay backgrounds |
**When to use:**
- Page backgrounds (`--background`)
- Content cards and panels (`--card`)
- Tooltips, dropdowns, modals (`--popover`)
### ✏️ Text & Content Colors
| Variable | Light Theme | Dark Theme | Usage |
| -------------------- | ----------- | ------------ | ------------------------ |
| `--foreground` | Dark gray | Light gray | Primary text color |
| `--muted` | Light gray | Very dark | Subtle background areas |
| `--muted-foreground` | Medium gray | Medium light | Secondary text, captions |
**When to use:**
- Main body text (`--foreground`)
- Helper text, placeholders (`--muted-foreground`)
- Disabled text states
- Subtle background sections (`--muted`)
### 🎯 Interactive Elements
| Variable | Light Theme | Dark Theme | Usage |
| --------------------- | ------------ | ----------- | ---------------------------- |
| `--accent` | Light gray | Very dark | Hover states, selected items |
| `--accent-foreground` | Dark gray | Light gray | Text on accent backgrounds |
| `--border` | Medium light | Medium dark | Dividers, input borders |
| `--input` | Medium light | Medium dark | Form input backgrounds |
| `--ring` | Blue | Blue | Focus outlines |
**When to use:**
- Hover states (`--accent`)
- Form field borders (`--border`)
- Input field backgrounds (`--input`)
- Focus indicators (`--ring`)
### ⚠️ Feedback Colors
| Variable | Light Theme | Dark Theme | Usage |
| -------------------------- | ----------- | ---------- | ------------------------------- |
| `--destructive` | Very dark | Red | Error states, dangerous actions |
| `--destructive-foreground` | White | White | Text on destructive backgrounds |
**When to use:**
- Error messages
- Delete buttons
- Warning alerts
- Validation failures
### 📊 Data Visualization
| Variable | Purpose |
| ----------- | --------------------------------------- |
| `--chart-1` | Primary data series (golden) |
| `--chart-2` | Secondary data series (purple) |
| `--chart-3` | Tertiary data series (light) |
| `--chart-4` | Quaternary data series (purple variant) |
| `--chart-5` | Quinary data series (golden variant) |
**When to use:**
- Charts and graphs
- Data visualization
- Progress indicators
- Statistical displays
### 🔧 Sidebar System
| Variable | Usage |
| ------------------------------ | ---------------------------- |
| `--sidebar` | Sidebar background |
| `--sidebar-foreground` | Sidebar text |
| `--sidebar-primary` | Active sidebar items |
| `--sidebar-primary-foreground` | Text on active sidebar items |
| `--sidebar-accent` | Sidebar hover states |
| `--sidebar-accent-foreground` | Text on sidebar hover states |
| `--sidebar-border` | Sidebar dividers |
| `--sidebar-ring` | Sidebar focus indicators |
## Best Practices
### ✅ Do's
1. **Always pair colors correctly:**
```css
/* Correct */
background: var(--primary);
color: var(--primary-foreground);
```
2. **Use semantic meaning:**
- Primary = main actions
- Secondary = supporting actions
- Destructive = dangerous/delete actions
- Muted = less important content
3. **Respect the design system:**
- Use existing color tokens instead of custom colors
- Maintain consistency across components
### Don'ts
1. **Don't mix incompatible pairs:**
```css
/* Incorrect - poor contrast */
background: var(--primary);
color: var(--foreground);
```
2. **Don't use colors outside their intended purpose:**
- Don't use destructive colors for positive actions
- Don't use primary colors for secondary elements
3. **Don't hardcode color values:**
```css
/* Bad */
color: #333333;
/* Good */
color: var(--foreground);
```
## Theme Switching
The color system automatically adapts between light and dark themes when the `.dark` class is applied to a parent element (typically `<html>` or `<body>`):
```javascript
// Toggle dark mode
document.documentElement.classList.toggle("dark");
```
## Accessibility
- All color pairs meet WCAG contrast requirements
- Focus indicators use `--ring` for consistency
- Color is never the only means of conveying information
## Implementation Examples
### Button Variants
```css
/* Primary button */
.btn-primary {
background: var(--primary);
color: var(--primary-foreground);
border: 1px solid var(--primary);
}
/* Secondary button */
.btn-secondary {
background: var(--secondary);
color: var(--secondary-foreground);
border: 1px solid var(--border);
}
/* Destructive button */
.btn-destructive {
background: var(--destructive);
color: var(--destructive-foreground);
border: 1px solid var(--destructive);
}
```
### Form Elements
```css
/* Input field */
.input {
background: var(--input);
color: var(--foreground);
border: 1px solid var(--border);
}
.input:focus {
outline: 2px solid var(--ring);
}
```
### Cards and Containers
```css
/* Content card */
.card {
background: var(--card);
color: var(--card-foreground);
border: 1px solid var(--border);
}
/* Popover/Modal */
.popover {
background: var(--popover);
color: var(--popover-foreground);
box-shadow: var(--shadow-lg);
}
```
## Color Testing
To ensure proper contrast and accessibility:
1. Test both light and dark themes
2. Verify readability at different zoom levels
3. Check with colorblind simulation tools
4. Validate WCAG contrast ratios
## Z-Index Hierarchy
The application uses a structured z-index hierarchy to ensure proper layering of UI components:
| Component Type | Z-Index | Usage |
| ----------------- | -------- | ------------------------------------- |
| **Base Content** | `z-0` | Normal page content |
| **Overlays** | `z-50` | Dialog/Sheet backgrounds |
| **Modal Content** | `z-50` | Dialog/Sheet content |
| **Dropdowns** | `z-[60]` | Select, DropdownMenu, Popover content |
| **Tooltips** | `z-[70]` | Tooltip content (highest priority) |
### Rules
1. **Dialog/Sheet**: Use `z-50` for both overlay and content
2. **Interactive Elements**: Use `z-[60]` for dropdowns inside dialogs
3. **Tooltips**: Use `z-[70]` to appear above all other elements
4. **Always test**: Ensure Select/DropdownMenu works inside Dialog/Sheet
### Example
```tsx
// ✅ Correct: Select inside Dialog will appear above dialog content
<Dialog>
<DialogContent>
<Select>
<SelectContent className="z-[60]">
{" "}
{/* Higher than dialog */}
<SelectItem>Option 1</SelectItem>
</SelectContent>
</Select>
</DialogContent>
</Dialog>
```
---
_This color system is designed to provide a consistent, accessible, and beautiful user experience across all themes and components._
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
"@matejmazur/react-katex": "^3.1.3", "@matejmazur/react-katex": "^3.1.3",
"@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-label": "^2.1.7", "@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-popover": "^1.1.14", "@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-radio-group": "^1.3.7", "@radix-ui/react-radio-group": "^1.3.7",
......
...@@ -35,6 +35,9 @@ importers: ...@@ -35,6 +35,9 @@ importers:
'@radix-ui/react-dialog': '@radix-ui/react-dialog':
specifier: ^1.1.14 specifier: ^1.1.14
version: 1.1.14(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 1.1.14(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-dropdown-menu':
specifier: ^2.1.15
version: 2.1.15(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-label': '@radix-ui/react-label':
specifier: ^2.1.7 specifier: ^2.1.7
version: 2.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 2.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
...@@ -805,6 +808,19 @@ packages: ...@@ -805,6 +808,19 @@ packages:
'@types/react-dom': '@types/react-dom':
optional: true optional: true
'@radix-ui/react-dropdown-menu@2.1.15':
resolution: {integrity: sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-focus-guards@1.1.2': '@radix-ui/react-focus-guards@1.1.2':
resolution: {integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==} resolution: {integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==}
peerDependencies: peerDependencies:
...@@ -849,6 +865,19 @@ packages: ...@@ -849,6 +865,19 @@ packages:
'@types/react-dom': '@types/react-dom':
optional: true optional: true
'@radix-ui/react-menu@2.1.15':
resolution: {integrity: sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-popover@1.1.14': '@radix-ui/react-popover@1.1.14':
resolution: {integrity: sha512-ODz16+1iIbGUfFEfKx2HTPKizg2MN39uIOV8MXeHnmdd3i/N9Wt7vU46wbHsqA0xoaQyXVcs0KIlBdOA2Y95bw==} resolution: {integrity: sha512-ODz16+1iIbGUfFEfKx2HTPKizg2MN39uIOV8MXeHnmdd3i/N9Wt7vU46wbHsqA0xoaQyXVcs0KIlBdOA2Y95bw==}
peerDependencies: peerDependencies:
...@@ -4052,6 +4081,21 @@ snapshots: ...@@ -4052,6 +4081,21 @@ snapshots:
'@types/react': 18.3.23 '@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23) '@types/react-dom': 18.3.7(@types/react@18.3.23)
'@radix-ui/react-dropdown-menu@2.1.15(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.2
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-id': 1.1.1(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-menu': 2.1.15(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.23)(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
'@radix-ui/react-focus-guards@1.1.2(@types/react@18.3.23)(react@18.3.1)': '@radix-ui/react-focus-guards@1.1.2(@types/react@18.3.23)(react@18.3.1)':
dependencies: dependencies:
react: 18.3.1 react: 18.3.1
...@@ -4085,6 +4129,32 @@ snapshots: ...@@ -4085,6 +4129,32 @@ snapshots:
'@types/react': 18.3.23 '@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23) '@types/react-dom': 18.3.7(@types/react@18.3.23)
'@radix-ui/react-menu@2.1.15(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.2
'@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-direction': 1.1.1(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-focus-guards': 1.1.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-id': 1.1.1(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-popper': 1.2.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@18.3.1)
aria-hidden: 1.2.6
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-remove-scroll: 2.7.1(@types/react@18.3.23)(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
'@radix-ui/react-popover@1.1.14(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@radix-ui/react-popover@1.1.14(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies: dependencies:
'@radix-ui/primitive': 1.1.2 '@radix-ui/primitive': 1.1.2
......
...@@ -52,7 +52,7 @@ const BaseDialog = observer((props: Props) => { ...@@ -52,7 +52,7 @@ const BaseDialog = observer((props: Props) => {
return ( return (
<div <div
className={cn( className={cn(
"fixed top-0 left-0 flex flex-col justify-start items-center w-full h-full pt-16 pb-8 px-4 z-1000 overflow-x-hidden overflow-y-scroll transition-all hide-scrollbar bg-foreground/60", "fixed top-0 left-0 flex flex-col justify-start items-center w-full h-full pt-16 pb-8 px-4 z-50 overflow-x-hidden overflow-y-scroll transition-all hide-scrollbar bg-foreground/60",
className, className,
)} )}
onMouseDown={handleSpaceClicked} onMouseDown={handleSpaceClicked}
......
...@@ -38,7 +38,12 @@ const HomeSidebar = observer((props: Props) => { ...@@ -38,7 +38,12 @@ const HomeSidebar = observer((props: Props) => {
); );
return ( return (
<aside className={cn("relative w-full h-full overflow-auto flex flex-col justify-start items-start", props.className)}> <aside
className={cn(
"relative w-full h-full overflow-auto flex flex-col justify-start items-start bg-sidebar text-sidebar-foreground",
props.className,
)}
>
<SearchBar /> <SearchBar />
<div className="mt-1 px-1 w-full"> <div className="mt-1 px-1 w-full">
<StatisticsView /> <StatisticsView />
......
...@@ -9,7 +9,7 @@ import memoFilterStore from "@/store/v2/memoFilter"; ...@@ -9,7 +9,7 @@ import memoFilterStore from "@/store/v2/memoFilter";
import { Shortcut } from "@/types/proto/api/v1/shortcut_service"; import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import showCreateShortcutDialog from "../CreateShortcutDialog"; import showCreateShortcutDialog from "../CreateShortcutDialog";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../ui/dropdown-menu";
const emojiRegex = /^(\p{Emoji_Presentation}|\p{Emoji}\uFE0F)$/u; const emojiRegex = /^(\p{Emoji_Presentation}|\p{Emoji}\uFE0F)$/u;
...@@ -64,35 +64,27 @@ const ShortcutsSection = observer(() => { ...@@ -64,35 +64,27 @@ const ShortcutsSection = observer(() => {
className="shrink-0 w-full text-sm rounded-md leading-6 flex flex-row justify-between items-center select-none gap-2 text-muted-foreground" className="shrink-0 w-full text-sm rounded-md leading-6 flex flex-row justify-between items-center select-none gap-2 text-muted-foreground"
> >
<span <span
className={cn("truncate cursor-pointer opacity-80", selected && "text-primary font-medium")} className={cn("truncate cursor-pointer text-muted-foreground", selected && "text-primary font-medium")}
onClick={() => (selected ? memoFilterStore.setShortcut(undefined) : memoFilterStore.setShortcut(shortcutId))} onClick={() => (selected ? memoFilterStore.setShortcut(undefined) : memoFilterStore.setShortcut(shortcutId))}
> >
{emoji && <span className="text-base mr-1">{emoji}</span>} {emoji && <span className="text-base mr-1">{emoji}</span>}
{title.trim()} {title.trim()}
</span> </span>
<Popover> <DropdownMenu>
<PopoverTrigger asChild> <DropdownMenuTrigger asChild>
<MoreVerticalIcon className="w-4 h-auto shrink-0 opacity-40 cursor-pointer hover:opacity-70" /> <MoreVerticalIcon className="w-4 h-auto shrink-0 text-muted-foreground cursor-pointer hover:text-foreground" />
</PopoverTrigger> </DropdownMenuTrigger>
<PopoverContent align="end" alignOffset={-12}> <DropdownMenuContent align="end" alignOffset={-12}>
<div className="flex flex-col text-sm gap-0.5"> <DropdownMenuItem onClick={() => showCreateShortcutDialog({ shortcut })}>
<button
onClick={() => showCreateShortcutDialog({ shortcut })}
className="flex items-center gap-2 px-2 py-1 text-left hover:bg-muted outline-none rounded"
>
<Edit3Icon className="w-4 h-auto" /> <Edit3Icon className="w-4 h-auto" />
{t("common.edit")} {t("common.edit")}
</button> </DropdownMenuItem>
<button <DropdownMenuItem onClick={() => handleDeleteShortcut(shortcut)}>
onClick={() => handleDeleteShortcut(shortcut)}
className="flex items-center gap-2 px-2 py-1 text-left text-destructive hover:bg-muted outline-none rounded"
>
<TrashIcon className="w-4 h-auto" /> <TrashIcon className="w-4 h-auto" />
{t("common.delete")} {t("common.delete")}
</button> </DropdownMenuItem>
</div> </DropdownMenuContent>
</PopoverContent> </DropdownMenu>
</Popover>
</div> </div>
); );
})} })}
......
...@@ -10,6 +10,7 @@ import memoFilterStore, { MemoFilter } from "@/store/v2/memoFilter"; ...@@ -10,6 +10,7 @@ import memoFilterStore, { MemoFilter } from "@/store/v2/memoFilter";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import showRenameTagDialog from "../RenameTagDialog"; import showRenameTagDialog from "../RenameTagDialog";
import TagTree from "../TagTree"; import TagTree from "../TagTree";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../ui/dropdown-menu";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
interface Props { interface Props {
...@@ -53,7 +54,7 @@ const TagsSection = observer((props: Props) => { ...@@ -53,7 +54,7 @@ const TagsSection = observer((props: Props) => {
{tags.length > 0 && ( {tags.length > 0 && (
<Popover> <Popover>
<PopoverTrigger> <PopoverTrigger>
<MoreVerticalIcon className="w-4 h-auto shrink-0 opacity-60" /> <MoreVerticalIcon className="w-4 h-auto shrink-0 text-muted-foreground" />
</PopoverTrigger> </PopoverTrigger>
<PopoverContent align="end" alignOffset={-12}> <PopoverContent align="end" alignOffset={-12}>
<div className="w-auto flex flex-row justify-between items-center gap-2 p-1"> <div className="w-auto flex flex-row justify-between items-center gap-2 p-1">
...@@ -74,32 +75,24 @@ const TagsSection = observer((props: Props) => { ...@@ -74,32 +75,24 @@ const TagsSection = observer((props: Props) => {
key={tag} key={tag}
className="shrink-0 w-auto max-w-full text-sm rounded-md leading-6 flex flex-row justify-start items-center select-none hover:opacity-80 text-muted-foreground" className="shrink-0 w-auto max-w-full text-sm rounded-md leading-6 flex flex-row justify-start items-center select-none hover:opacity-80 text-muted-foreground"
> >
<Popover> <DropdownMenu>
<PopoverTrigger asChild> <DropdownMenuTrigger asChild>
<div className="shrink-0 group cursor-pointer"> <div className="shrink-0 group cursor-pointer">
<HashIcon className="group-hover:hidden w-4 h-auto shrink-0 opacity-40" /> <HashIcon className="group-hover:hidden w-4 h-auto shrink-0 text-muted-foreground" />
<MoreVerticalIcon className="hidden group-hover:block w-4 h-auto shrink-0 opacity-60" /> <MoreVerticalIcon className="hidden group-hover:block w-4 h-auto shrink-0 text-muted-foreground" />
</div> </div>
</PopoverTrigger> </DropdownMenuTrigger>
<PopoverContent align="start" sideOffset={2}> <DropdownMenuContent align="start" sideOffset={2}>
<div className="flex flex-col text-sm gap-0.5"> <DropdownMenuItem onClick={() => showRenameTagDialog({ tag: tag })}>
<button
onClick={() => showRenameTagDialog({ tag: tag })}
className="flex items-center gap-2 px-2 py-1 text-left hover:bg-muted outline-none rounded"
>
<Edit3Icon className="w-4 h-auto" /> <Edit3Icon className="w-4 h-auto" />
{t("common.rename")} {t("common.rename")}
</button> </DropdownMenuItem>
<button <DropdownMenuItem onClick={() => handleDeleteTag(tag)}>
onClick={() => handleDeleteTag(tag)}
className="flex items-center gap-2 px-2 py-1 text-left text-destructive hover:bg-muted outline-none rounded"
>
<TrashIcon className="w-4 h-auto" /> <TrashIcon className="w-4 h-auto" />
{t("common.delete")} {t("common.delete")}
</button> </DropdownMenuItem>
</div> </DropdownMenuContent>
</PopoverContent> </DropdownMenu>
</Popover>
<div <div
className={cn("inline-flex flex-nowrap ml-0.5 gap-0.5 cursor-pointer max-w-[calc(100%-16px)]")} className={cn("inline-flex flex-nowrap ml-0.5 gap-0.5 cursor-pointer max-w-[calc(100%-16px)]")}
onClick={() => handleTagClick(tag)} onClick={() => handleTagClick(tag)}
......
...@@ -15,13 +15,13 @@ import toast from "react-hot-toast"; ...@@ -15,13 +15,13 @@ import toast from "react-hot-toast";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import { markdownServiceClient } from "@/grpcweb"; import { markdownServiceClient } from "@/grpcweb";
import useNavigateTo from "@/hooks/useNavigateTo"; import useNavigateTo from "@/hooks/useNavigateTo";
import { cn } from "@/lib/utils";
import { memoStore, userStore } from "@/store/v2"; import { memoStore, userStore } from "@/store/v2";
import { State } from "@/types/proto/api/v1/common"; import { State } from "@/types/proto/api/v1/common";
import { NodeType } from "@/types/proto/api/v1/markdown_service"; import { NodeType } from "@/types/proto/api/v1/markdown_service";
import { Memo } from "@/types/proto/api/v1/memo_service"; import { Memo } from "@/types/proto/api/v1/memo_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; import { Button } from "./ui/button";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "./ui/dropdown-menu";
interface Props { interface Props {
memo: Memo; memo: Memo;
...@@ -163,72 +163,55 @@ const MemoActionMenu = observer((props: Props) => { ...@@ -163,72 +163,55 @@ const MemoActionMenu = observer((props: Props) => {
}; };
return ( return (
<Popover> <DropdownMenu>
<PopoverTrigger asChild> <DropdownMenuTrigger asChild>
<span className={cn("flex justify-center items-center rounded-full hover:opacity-70 cursor-pointer", props.className)}> <Button variant="ghost" size="icon" className="size-4">
<MoreVerticalIcon className="w-4 h-4 mx-auto text-muted-foreground" /> <MoreVerticalIcon className="text-muted-foreground" />
</span> </Button>
</PopoverTrigger> </DropdownMenuTrigger>
<PopoverContent align="end" sideOffset={2}> <DropdownMenuContent align="end" sideOffset={2}>
<div className="flex flex-col text-sm gap-0.5">
{!readonly && !isArchived && ( {!readonly && !isArchived && (
<> <>
{!isComment && ( {!isComment && (
<button <DropdownMenuItem onClick={handleTogglePinMemoBtnClick}>
onClick={handleTogglePinMemoBtnClick}
className="flex items-center gap-2 px-2 py-1 text-left hover:bg-muted outline-none rounded"
>
{memo.pinned ? <BookmarkMinusIcon className="w-4 h-auto" /> : <BookmarkPlusIcon className="w-4 h-auto" />} {memo.pinned ? <BookmarkMinusIcon className="w-4 h-auto" /> : <BookmarkPlusIcon className="w-4 h-auto" />}
{memo.pinned ? t("common.unpin") : t("common.pin")} {memo.pinned ? t("common.unpin") : t("common.pin")}
</button> </DropdownMenuItem>
)} )}
<button <DropdownMenuItem onClick={handleEditMemoClick}>
onClick={handleEditMemoClick}
className="flex items-center gap-2 px-2 py-1 text-left hover:bg-muted outline-none rounded"
>
<Edit3Icon className="w-4 h-auto" /> <Edit3Icon className="w-4 h-auto" />
{t("common.edit")} {t("common.edit")}
</button> </DropdownMenuItem>
</> </>
)} )}
{!isArchived && ( {!isArchived && (
<button onClick={handleCopyLink} className="flex items-center gap-2 px-2 py-1 text-left hover:bg-muted outline-none rounded"> <DropdownMenuItem onClick={handleCopyLink}>
<CopyIcon className="w-4 h-auto" /> <CopyIcon className="w-4 h-auto" />
{t("memo.copy-link")} {t("memo.copy-link")}
</button> </DropdownMenuItem>
)} )}
{!readonly && ( {!readonly && (
<> <>
{!isArchived && !isComment && hasCompletedTaskList && ( {!isArchived && !isComment && hasCompletedTaskList && (
<button <DropdownMenuItem onClick={handleRemoveCompletedTaskListItemsClick}>
onClick={handleRemoveCompletedTaskListItemsClick}
className="flex items-center gap-2 px-2 py-1 text-left text-primary hover:bg-muted outline-none rounded"
>
<SquareCheckIcon className="w-4 h-auto" /> <SquareCheckIcon className="w-4 h-auto" />
{t("memo.remove-completed-task-list-items")} {t("memo.remove-completed-task-list-items")}
</button> </DropdownMenuItem>
)} )}
{!isComment && ( {!isComment && (
<button <DropdownMenuItem onClick={handleToggleMemoStatusClick}>
onClick={handleToggleMemoStatusClick}
className="flex items-center gap-2 px-2 py-1 text-left text-primary hover:bg-muted outline-none rounded"
>
{isArchived ? <ArchiveRestoreIcon className="w-4 h-auto" /> : <ArchiveIcon className="w-4 h-auto" />} {isArchived ? <ArchiveRestoreIcon className="w-4 h-auto" /> : <ArchiveIcon className="w-4 h-auto" />}
{isArchived ? t("common.restore") : t("common.archive")} {isArchived ? t("common.restore") : t("common.archive")}
</button> </DropdownMenuItem>
)} )}
<button <DropdownMenuItem onClick={handleDeleteMemoClick}>
onClick={handleDeleteMemoClick}
className="flex items-center gap-2 px-2 py-1 text-left text-destructive hover:bg-muted outline-none rounded"
>
<TrashIcon className="w-4 h-auto" /> <TrashIcon className="w-4 h-auto" />
{t("common.delete")} {t("common.delete")}
</button> </DropdownMenuItem>
</> </>
)} )}
</div> </DropdownMenuContent>
</PopoverContent> </DropdownMenu>
</Popover>
); );
}); });
......
...@@ -16,7 +16,9 @@ const MemoAttachment: React.FC<Props> = (props: Props) => { ...@@ -16,7 +16,9 @@ const MemoAttachment: React.FC<Props> = (props: Props) => {
}; };
return ( return (
<div className={`w-auto flex flex-row justify-start items-center text-muted-foreground hover:opacity-80 ${className}`}> <div
className={`w-auto flex flex-row justify-start items-center text-muted-foreground hover:text-foreground hover:bg-accent rounded px-2 py-1 transition-colors ${className}`}
>
{attachment.type.startsWith("audio") ? ( {attachment.type.startsWith("audio") ? (
<audio src={attachmentUrl} controls></audio> <audio src={attachmentUrl} controls></audio>
) : ( ) : (
......
...@@ -34,7 +34,10 @@ const MemoAttachmentListView = ({ attachments = [] }: { attachments: Attachment[ ...@@ -34,7 +34,10 @@ const MemoAttachmentListView = ({ attachments = [] }: { attachments: Attachment[
if (type === "image/*") { if (type === "image/*") {
return ( return (
<img <img
className={cn("cursor-pointer h-full w-auto rounded-lg border border-border object-contain hover:opacity-80", className)} className={cn(
"cursor-pointer h-full w-auto rounded-lg border border-border object-contain hover:border-accent transition-colors",
className,
)}
src={attachment.externalLink ? attachmentUrl : attachmentUrl + "?thumbnail=true"} src={attachment.externalLink ? attachmentUrl : attachmentUrl + "?thumbnail=true"}
onClick={() => handleImageClick(attachmentUrl)} onClick={() => handleImageClick(attachmentUrl)}
decoding="async" decoding="async"
...@@ -44,7 +47,10 @@ const MemoAttachmentListView = ({ attachments = [] }: { attachments: Attachment[ ...@@ -44,7 +47,10 @@ const MemoAttachmentListView = ({ attachments = [] }: { attachments: Attachment[
} else if (type === "video/*") { } else if (type === "video/*") {
return ( return (
<video <video
className={cn("cursor-pointer h-full w-auto rounded-lg border border-border object-contain bg-popover", className)} className={cn(
"cursor-pointer h-full w-auto rounded-lg border border-border object-contain bg-popover hover:border-accent transition-colors",
className,
)}
preload="metadata" preload="metadata"
crossOrigin="anonymous" crossOrigin="anonymous"
src={attachmentUrl} src={attachmentUrl}
......
...@@ -8,7 +8,7 @@ interface Props extends BaseProps { ...@@ -8,7 +8,7 @@ interface Props extends BaseProps {
const Blockquote: React.FC<Props> = ({ children }: Props) => { const Blockquote: React.FC<Props> = ({ children }: Props) => {
return ( return (
<blockquote className="p-2 border-s-4 rounded border-muted bg-muted"> <blockquote className="p-2 border-l-4 rounded border-border bg-muted/50 text-muted-foreground">
{children.map((child, index) => ( {children.map((child, index) => (
<Renderer key={`${child.type}-${index}`} index={String(index)} node={child} /> <Renderer key={`${child.type}-${index}`} index={String(index)} node={child} />
))} ))}
......
...@@ -3,7 +3,7 @@ interface Props { ...@@ -3,7 +3,7 @@ interface Props {
} }
const Code: React.FC<Props> = ({ content }: Props) => { const Code: React.FC<Props> = ({ content }: Props) => {
return <code className="inline break-all px-1 font-mono text-sm rounded opacity-80 bg-muted">{content}</code>; return <code className="inline break-all px-1 font-mono text-sm rounded bg-muted text-muted-foreground">{content}</code>;
}; };
export default Code; export default Code;
...@@ -59,16 +59,16 @@ const CodeBlock: React.FC<Props> = ({ language, content }: Props) => { ...@@ -59,16 +59,16 @@ const CodeBlock: React.FC<Props> = ({ language, content }: Props) => {
}, [content]); }, [content]);
return ( return (
<div className="w-full my-1 bg-background border-2 border-l-4 border-popover rounded-md relative"> <div className="w-full my-1 bg-card border border-border rounded-md relative">
<div className="w-full px-2 mt-1 flex flex-row justify-between items-center text-accent-foreground/60"> <div className="w-full px-2 py-0.5 flex flex-row justify-between items-center text-muted-foreground">
<span className="text-sm font-mono">{formatedLanguage}</span> <span className="text-xs font-mono">{formatedLanguage}</span>
<CopyIcon className="w-4 h-auto cursor-pointer hover:opacity-80" onClick={handleCopyButtonClick} /> <CopyIcon className="w-3 h-auto cursor-pointer hover:text-foreground" onClick={handleCopyButtonClick} />
</div> </div>
<div className="overflow-auto"> <div className="overflow-auto">
<pre className={cn("no-wrap overflow-auto", "w-full p-2 bg-accent/5 relative")}> <pre className={cn("no-wrap overflow-auto", "w-full p-2 bg-muted/50 relative")}>
<code <code
className={cn(`language-${formatedLanguage}`, "block text-sm leading-5")} className={cn(`language-${formatedLanguage}`, "block text-sm leading-5 text-foreground")}
dangerouslySetInnerHTML={{ __html: highlightedCode }} dangerouslySetInnerHTML={{ __html: highlightedCode }}
></code> ></code>
</pre> </pre>
......
...@@ -67,19 +67,24 @@ const EmbeddedMemo = observer(({ resourceId: uid, params: paramsStr }: Props) => ...@@ -67,19 +67,24 @@ const EmbeddedMemo = observer(({ resourceId: uid, params: paramsStr }: Props) =>
}; };
return ( return (
<div className="relative flex flex-col justify-start items-start w-full px-3 py-2 bg-popover rounded-lg border border-border hover:shadow"> <div className="relative flex flex-col justify-start items-start w-full px-3 py-2 bg-card rounded-lg border border-border hover:shadow-md transition-shadow">
<div className="w-full mb-1 flex flex-row justify-between items-center text-muted-foreground"> <div className="w-full mb-1 flex flex-row justify-between items-center text-muted-foreground">
<div className="text-sm leading-5 select-none"> <div className="text-sm leading-5 select-none">
<relative-time datetime={memo.displayTime?.toISOString()} format="datetime"></relative-time> <relative-time datetime={memo.displayTime?.toISOString()} format="datetime"></relative-time>
</div> </div>
<div className="flex justify-end items-center gap-1"> <div className="flex justify-end items-center gap-1">
<span <span
className="text-xs opacity-60 leading-5 cursor-pointer hover:opacity-80" className="text-xs text-muted-foreground leading-5 cursor-pointer hover:text-foreground"
onClick={() => copyMemoUid(extractMemoIdFromName(memo.name))} onClick={() => copyMemoUid(extractMemoIdFromName(memo.name))}
> >
{extractMemoIdFromName(memo.name).slice(0, 6)} {extractMemoIdFromName(memo.name).slice(0, 6)}
</span> </span>
<Link className="opacity-60 hover:opacity-80" to={`/${memo.name}`} state={{ from: context.parentPage }} viewTransition> <Link
className="text-muted-foreground hover:text-foreground"
to={`/${memo.name}`}
state={{ from: context.parentPage }}
viewTransition
>
<ArrowUpRightIcon className="w-5 h-auto" /> <ArrowUpRightIcon className="w-5 h-auto" />
</Link> </Link>
</div> </div>
......
...@@ -3,7 +3,7 @@ interface Props { ...@@ -3,7 +3,7 @@ interface Props {
} }
const Highlight: React.FC<Props> = ({ content }: Props) => { const Highlight: React.FC<Props> = ({ content }: Props) => {
return <mark>{content}</mark>; return <mark className="bg-yellow-200 text-foreground px-1 rounded">{content}</mark>;
}; };
export default Highlight; export default Highlight;
...@@ -43,7 +43,10 @@ const MermaidBlock: React.FC<Props> = ({ content }: Props) => { ...@@ -43,7 +43,10 @@ const MermaidBlock: React.FC<Props> = ({ content }: Props) => {
}, [content]); }, [content]);
return ( return (
<pre ref={mermaidDockBlock} className="w-full p-2 whitespace-pre-wrap relative"> <pre
ref={mermaidDockBlock}
className="w-full p-2 whitespace-pre-wrap relative bg-card border border-border rounded text-card-foreground"
>
{content} {content}
</pre> </pre>
); );
......
...@@ -44,7 +44,7 @@ const ReferencedMemo = observer(({ resourceId: uid, params: paramsStr }: Props) ...@@ -44,7 +44,7 @@ const ReferencedMemo = observer(({ resourceId: uid, params: paramsStr }: Props)
return ( return (
<span <span
className="text-primary whitespace-nowrap cursor-pointer underline break-all hover:opacity-80 decoration-1" className="text-primary whitespace-nowrap cursor-pointer underline break-all hover:text-primary/80 decoration-1"
onClick={handleGotoMemoDetailPage} onClick={handleGotoMemoDetailPage}
> >
{displayContent} {displayContent}
......
...@@ -9,7 +9,10 @@ const Spoiler: React.FC<Props> = ({ content }: Props) => { ...@@ -9,7 +9,10 @@ const Spoiler: React.FC<Props> = ({ content }: Props) => {
const [isRevealed, setIsRevealed] = useState(false); const [isRevealed, setIsRevealed] = useState(false);
return ( return (
<span className={cn("inline cursor-pointer select-none", isRevealed ? "" : "bg-muted")} onClick={() => setIsRevealed(!isRevealed)}> <span
className={cn("inline cursor-pointer select-none", isRevealed ? "" : "bg-muted text-muted")}
onClick={() => setIsRevealed(!isRevealed)}
>
<span className={cn(isRevealed ? "opacity-100" : "opacity-0")}>{content}</span> <span className={cn(isRevealed ? "opacity-100" : "opacity-0")}>{content}</span>
</span> </span>
); );
......
...@@ -45,7 +45,7 @@ const Tag = observer(({ content }: Props) => { ...@@ -45,7 +45,7 @@ const Tag = observer(({ content }: Props) => {
return ( return (
<span <span
className={cn("inline-block w-auto text-primary", context.disableFilter ? "" : "cursor-pointer hover:opacity-80")} className={cn("inline-block w-auto text-primary", context.disableFilter ? "" : "cursor-pointer hover:text-primary/80")}
onClick={handleTagClick} onClick={handleTagClick}
> >
#{content} #{content}
......
...@@ -46,7 +46,7 @@ const TaskListItem = observer(({ node, complete, children }: Props) => { ...@@ -46,7 +46,7 @@ const TaskListItem = observer(({ node, complete, children }: Props) => {
onCheckedChange={(checked) => handleCheckboxChange(checked === true)} onCheckedChange={(checked) => handleCheckboxChange(checked === true)}
/> />
</span> </span>
<p className={cn(complete && "line-through opacity-80")}> <p className={cn(complete && "line-through text-muted-foreground")}>
{children.map((child, index) => ( {children.map((child, index) => (
<Renderer key={`${child.type}-${index}`} index={String(index)} node={child} /> <Renderer key={`${child.type}-${index}`} index={String(index)} node={child} />
))} ))}
......
...@@ -31,7 +31,7 @@ const MemoDisplaySettingMenu = observer(({ className }: Props) => { ...@@ -31,7 +31,7 @@ const MemoDisplaySettingMenu = observer(({ className }: Props) => {
}) })
} }
> >
<SelectTrigger className="w-32"> <SelectTrigger size="sm">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
...@@ -50,7 +50,7 @@ const MemoDisplaySettingMenu = observer(({ className }: Props) => { ...@@ -50,7 +50,7 @@ const MemoDisplaySettingMenu = observer(({ className }: Props) => {
}) })
} }
> >
<SelectTrigger className="w-32"> <SelectTrigger size="sm">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
......
...@@ -162,7 +162,7 @@ const AddMemoRelationPopover = (props: Props) => { ...@@ -162,7 +162,7 @@ const AddMemoRelationPopover = (props: Props) => {
placeholder={t("reference.search-placeholder")} placeholder={t("reference.search-placeholder")}
value={searchText} value={searchText}
onChange={(e) => setSearchText(e.target.value)} onChange={(e) => setSearchText(e.target.value)}
className="h-9 mb-2" className="mb-2"
/> />
<div className="max-h-[200px] overflow-y-auto"> <div className="max-h-[200px] overflow-y-auto">
{filteredMemos.length === 0 ? ( {filteredMemos.length === 0 ? (
......
...@@ -16,7 +16,7 @@ const MemoLocationView: React.FC<Props> = (props: Props) => { ...@@ -16,7 +16,7 @@ const MemoLocationView: React.FC<Props> = (props: Props) => {
return ( return (
<Popover open={popoverOpen} onOpenChange={setPopoverOpen}> <Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<p className="w-full flex flex-row gap-0.5 items-center text-muted-foreground"> <p className="w-full flex flex-row gap-0.5 items-center text-muted-foreground hover:text-foreground cursor-pointer transition-colors">
<MapPinIcon className="w-4 h-auto shrink-0" /> <MapPinIcon className="w-4 h-auto shrink-0" />
<span className="text-sm font-normal text-ellipsis whitespace-nowrap overflow-hidden"> <span className="text-sm font-normal text-ellipsis whitespace-nowrap overflow-hidden">
{location.placeholder ? location.placeholder : `[${location.latitude}, ${location.longitude}]`} {location.placeholder ? location.placeholder : `[${location.latitude}, ${location.longitude}]`}
......
...@@ -35,8 +35,8 @@ const MemoRelationListView = (props: Props) => { ...@@ -35,8 +35,8 @@ const MemoRelationListView = (props: Props) => {
{referencingMemoList.length > 0 && ( {referencingMemoList.length > 0 && (
<button <button
className={cn( className={cn(
"w-auto flex flex-row justify-start items-center text-xs gap-0.5 text-muted-foreground", "w-auto flex flex-row justify-start items-center text-xs gap-0.5 text-muted-foreground hover:text-foreground hover:bg-accent rounded px-1 py-0.5 transition-colors",
selectedTab === "referencing" && "text-foreground", selectedTab === "referencing" && "text-foreground bg-accent",
)} )}
onClick={() => setSelectedTab("referencing")} onClick={() => setSelectedTab("referencing")}
> >
...@@ -48,8 +48,8 @@ const MemoRelationListView = (props: Props) => { ...@@ -48,8 +48,8 @@ const MemoRelationListView = (props: Props) => {
{referencedMemoList.length > 0 && ( {referencedMemoList.length > 0 && (
<button <button
className={cn( className={cn(
"w-auto flex flex-row justify-start items-center text-xs gap-0.5 text-muted-foreground", "w-auto flex flex-row justify-start items-center text-xs gap-0.5 text-muted-foreground hover:text-foreground hover:bg-accent rounded px-1 py-0.5 transition-colors",
selectedTab === "referenced" && "text-foreground", selectedTab === "referenced" && "text-foreground bg-accent",
)} )}
onClick={() => setSelectedTab("referenced")} onClick={() => setSelectedTab("referenced")}
> >
...@@ -65,7 +65,7 @@ const MemoRelationListView = (props: Props) => { ...@@ -65,7 +65,7 @@ const MemoRelationListView = (props: Props) => {
return ( return (
<Link <Link
key={memo.name} key={memo.name}
className="w-auto max-w-full flex flex-row justify-start items-center text-sm leading-5 text-muted-foreground hover:underline" className="w-full flex flex-row justify-start items-center text-sm leading-5 text-muted-foreground hover:text-foreground hover:bg-accent rounded px-2 py-1 transition-colors"
to={`/${memo.name}`} to={`/${memo.name}`}
viewTransition viewTransition
state={{ state={{
...@@ -87,7 +87,7 @@ const MemoRelationListView = (props: Props) => { ...@@ -87,7 +87,7 @@ const MemoRelationListView = (props: Props) => {
return ( return (
<Link <Link
key={memo.name} key={memo.name}
className="w-auto max-w-full flex flex-row justify-start items-center text-sm leading-5 text-muted-foreground hover:underline" className="w-full flex flex-row justify-start items-center text-sm leading-5 text-muted-foreground hover:text-foreground hover:bg-accent rounded px-2 py-1 transition-colors"
to={`/${memo.name}`} to={`/${memo.name}`}
viewTransition viewTransition
state={{ state={{
......
...@@ -131,7 +131,8 @@ const MemoView: React.FC<Props> = observer((props: Props) => { ...@@ -131,7 +131,8 @@ const MemoView: React.FC<Props> = observer((props: Props) => {
) : ( ) : (
<div <div
className={cn( className={cn(
"group relative flex flex-col justify-start items-start w-full px-4 py-3 mb-2 gap-2 bg-card rounded-lg border border-border", "group relative flex flex-col justify-start items-start w-full px-4 py-3 mb-2 gap-2 bg-card text-card-foreground rounded-lg border border-border transition-colors",
"hover:bg-accent hover:text-accent-foreground hover:border-accent",
className, className,
)} )}
> >
...@@ -139,19 +140,23 @@ const MemoView: React.FC<Props> = observer((props: Props) => { ...@@ -139,19 +140,23 @@ const MemoView: React.FC<Props> = observer((props: Props) => {
<div className="w-auto max-w-[calc(100%-8rem)] grow flex flex-row justify-start items-center"> <div className="w-auto max-w-[calc(100%-8rem)] grow flex flex-row justify-start items-center">
{props.showCreator && creator ? ( {props.showCreator && creator ? (
<div className="w-full flex flex-row justify-start items-center"> <div className="w-full flex flex-row justify-start items-center">
<Link className="w-auto hover:opacity-80" to={`/u/${encodeURIComponent(creator.username)}`} viewTransition> <Link
className="w-auto hover:bg-accent hover:text-accent-foreground rounded-md p-1 transition-colors"
to={`/u/${encodeURIComponent(creator.username)}`}
viewTransition
>
<UserAvatar className="mr-2 shrink-0" avatarUrl={creator.avatarUrl} /> <UserAvatar className="mr-2 shrink-0" avatarUrl={creator.avatarUrl} />
</Link> </Link>
<div className="w-full flex flex-col justify-center items-start"> <div className="w-full flex flex-col justify-center items-start">
<Link <Link
className="w-full block leading-tight hover:opacity-80 truncate text-muted-foreground" className="w-full block leading-tight hover:bg-accent hover:text-accent-foreground rounded-md px-2 py-1 transition-colors truncate text-muted-foreground"
to={`/u/${encodeURIComponent(creator.username)}`} to={`/u/${encodeURIComponent(creator.username)}`}
viewTransition viewTransition
> >
{creator.displayName || creator.username} {creator.displayName || creator.username}
</Link> </Link>
<div <div
className="w-auto -mt-0.5 text-xs leading-tight text-muted-foreground select-none cursor-pointer" className="w-auto -mt-0.5 text-xs leading-tight text-muted-foreground select-none cursor-pointer hover:text-foreground transition-colors"
onClick={handleGotoMemoDetailPage} onClick={handleGotoMemoDetailPage}
> >
{displayTime} {displayTime}
...@@ -160,7 +165,7 @@ const MemoView: React.FC<Props> = observer((props: Props) => { ...@@ -160,7 +165,7 @@ const MemoView: React.FC<Props> = observer((props: Props) => {
</div> </div>
) : ( ) : (
<div <div
className="w-full text-sm leading-tight text-muted-foreground select-none cursor-pointer" className="w-full text-sm leading-tight text-muted-foreground select-none cursor-pointer hover:text-foreground transition-colors"
onClick={handleGotoMemoDetailPage} onClick={handleGotoMemoDetailPage}
> >
{displayTime} {displayTime}
...@@ -172,7 +177,7 @@ const MemoView: React.FC<Props> = observer((props: Props) => { ...@@ -172,7 +177,7 @@ const MemoView: React.FC<Props> = observer((props: Props) => {
{props.showVisibility && memo.visibility !== Visibility.PRIVATE && ( {props.showVisibility && memo.visibility !== Visibility.PRIVATE && (
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<span className="flex justify-center items-center hover:opacity-70"> <span className="flex justify-center items-center hover:bg-accent hover:text-accent-foreground rounded-md p-1 transition-colors">
<VisibilityIcon visibility={memo.visibility} /> <VisibilityIcon visibility={memo.visibility} />
</span> </span>
</TooltipTrigger> </TooltipTrigger>
...@@ -184,7 +189,7 @@ const MemoView: React.FC<Props> = observer((props: Props) => { ...@@ -184,7 +189,7 @@ const MemoView: React.FC<Props> = observer((props: Props) => {
{!isInMemoDetailPage && (workspaceMemoRelatedSetting.enableComment || commentAmount > 0) && ( {!isInMemoDetailPage && (workspaceMemoRelatedSetting.enableComment || commentAmount > 0) && (
<Link <Link
className={cn( className={cn(
"flex flex-row justify-start items-center hover:opacity-70", "flex flex-row justify-start items-center hover:bg-accent hover:text-accent-foreground rounded-md p-1 transition-colors",
commentAmount === 0 && "invisible group-hover:visible", commentAmount === 0 && "invisible group-hover:visible",
)} )}
to={`/${memo.name}#comments`} to={`/${memo.name}#comments`}
...@@ -216,7 +221,7 @@ const MemoView: React.FC<Props> = observer((props: Props) => { ...@@ -216,7 +221,7 @@ const MemoView: React.FC<Props> = observer((props: Props) => {
<EyeOffIcon className="w-4 h-auto text-primary" onClick={() => setShowNSFWContent(false)} /> <EyeOffIcon className="w-4 h-auto text-primary" onClick={() => setShowNSFWContent(false)} />
</span> </span>
)} )}
<MemoActionMenu className="-ml-1" memo={memo} readonly={readonly} onEdit={() => setShowEditor(true)} /> <MemoActionMenu memo={memo} readonly={readonly} onEdit={() => setShowEditor(true)} />
</div> </div>
</div> </div>
<div <div
...@@ -244,7 +249,7 @@ const MemoView: React.FC<Props> = observer((props: Props) => { ...@@ -244,7 +249,7 @@ const MemoView: React.FC<Props> = observer((props: Props) => {
<> <>
<div className="absolute inset-0 bg-transparent" /> <div className="absolute inset-0 bg-transparent" />
<button <button
className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 py-2 px-4 text-sm text-muted-foreground hover:text-foreground border border-border rounded-lg bg-card" className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 py-2 px-4 text-sm text-muted-foreground hover:text-foreground hover:bg-accent hover:border-accent border border-border rounded-lg bg-card transition-colors"
onClick={() => setShowNSFWContent(true)} onClick={() => setShowNSFWContent(true)}
> >
{t("memo.click-to-show-nsfw-content")} {t("memo.click-to-show-nsfw-content")}
......
...@@ -40,25 +40,25 @@ const Navigation = observer((props: Props) => { ...@@ -40,25 +40,25 @@ const Navigation = observer((props: Props) => {
id: "header-memos", id: "header-memos",
path: Routes.ROOT, path: Routes.ROOT,
title: t("common.memos"), title: t("common.memos"),
icon: <LibraryIcon className="w-6 h-auto opacity-70 shrink-0" />, icon: <LibraryIcon className="w-6 h-auto shrink-0" />,
}; };
const exploreNavLink: NavLinkItem = { const exploreNavLink: NavLinkItem = {
id: "header-explore", id: "header-explore",
path: Routes.EXPLORE, path: Routes.EXPLORE,
title: t("common.explore"), title: t("common.explore"),
icon: <EarthIcon className="w-6 h-auto opacity-70 shrink-0" />, icon: <EarthIcon className="w-6 h-auto shrink-0" />,
}; };
const attachmentsNavLink: NavLinkItem = { const attachmentsNavLink: NavLinkItem = {
id: "header-attachments", id: "header-attachments",
path: Routes.ATTACHMENTS, path: Routes.ATTACHMENTS,
title: t("common.attachments"), title: t("common.attachments"),
icon: <PaperclipIcon className="w-6 h-auto opacity-70 shrink-0" />, icon: <PaperclipIcon className="w-6 h-auto shrink-0" />,
}; };
const signInNavLink: NavLinkItem = { const signInNavLink: NavLinkItem = {
id: "header-auth", id: "header-auth",
path: Routes.AUTH, path: Routes.AUTH,
title: t("common.sign-in"), title: t("common.sign-in"),
icon: <UserCircleIcon className="w-6 h-auto opacity-70 shrink-0" />, icon: <UserCircleIcon className="w-6 h-auto shrink-0" />,
}; };
const navLinks: NavLinkItem[] = currentUser ? [homeNavLink, exploreNavLink, attachmentsNavLink] : [exploreNavLink, signInNavLink]; const navLinks: NavLinkItem[] = currentUser ? [homeNavLink, exploreNavLink, attachmentsNavLink] : [exploreNavLink, signInNavLink];
...@@ -78,9 +78,11 @@ const Navigation = observer((props: Props) => { ...@@ -78,9 +78,11 @@ const Navigation = observer((props: Props) => {
<NavLink <NavLink
className={({ isActive }) => className={({ isActive }) =>
cn( cn(
"px-2 py-2 rounded-2xl border flex flex-row items-center text-lg text-sidebar-foreground hover:border-border", "px-2 py-2 rounded-2xl border flex flex-row items-center text-lg text-sidebar-foreground transition-colors",
collapsed ? "" : "w-full px-4", collapsed ? "" : "w-full px-4",
isActive ? "bg-sidebar-primary text-sidebar-primary-foreground border-border" : "border-transparent", isActive
? "bg-sidebar-primary text-sidebar-primary-foreground border-sidebar-border"
: "border-transparent hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:border-sidebar-border",
) )
} }
key={navLink.id} key={navLink.id}
...@@ -106,7 +108,11 @@ const Navigation = observer((props: Props) => { ...@@ -106,7 +108,11 @@ const Navigation = observer((props: Props) => {
</NavLink> </NavLink>
))} ))}
</div> </div>
{currentUser && <UserBanner collapsed={collapsed} />} {currentUser && (
<div className={cn("w-full flex flex-col justify-end", props.collapsed ? "items-center" : "items-start pl-3")}>
<UserBanner collapsed={collapsed} />
</div>
)}
</header> </header>
); );
}); });
......
...@@ -57,7 +57,12 @@ const ReactionSelector = observer((props: Props) => { ...@@ -57,7 +57,12 @@ const ReactionSelector = observer((props: Props) => {
return ( return (
<Popover open={open} onOpenChange={setOpen}> <Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<span className={cn("h-7 w-7 flex justify-center items-center rounded-full border hover:opacity-70 cursor-pointer", className)}> <span
className={cn(
"h-7 w-7 flex justify-center items-center rounded-full border hover:bg-accent hover:text-accent-foreground cursor-pointer transition-colors",
className,
)}
>
<SmilePlusIcon className="w-4 h-4 mx-auto text-muted-foreground" /> <SmilePlusIcon className="w-4 h-4 mx-auto text-muted-foreground" />
</span> </span>
</PopoverTrigger> </PopoverTrigger>
...@@ -69,8 +74,8 @@ const ReactionSelector = observer((props: Props) => { ...@@ -69,8 +74,8 @@ const ReactionSelector = observer((props: Props) => {
<span <span
key={reactionType} key={reactionType}
className={cn( className={cn(
"inline-flex w-auto text-base cursor-pointer rounded px-1 text-muted-foreground hover:opacity-80", "inline-flex w-auto text-base cursor-pointer rounded px-1 text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors",
hasReacted(reactionType) && "bg-primary/10", hasReacted(reactionType) && "bg-primary text-primary-foreground",
)} )}
onClick={() => handleReactionClick(reactionType)} onClick={() => handleReactionClick(reactionType)}
> >
......
...@@ -14,7 +14,7 @@ import { State } from "@/types/proto/api/v1/common"; ...@@ -14,7 +14,7 @@ import { State } from "@/types/proto/api/v1/common";
import { User, User_Role } from "@/types/proto/api/v1/user_service"; import { User, User_Role } from "@/types/proto/api/v1/user_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import showCreateUserDialog from "../CreateUserDialog"; import showCreateUserDialog from "../CreateUserDialog";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../ui/dropdown-menu";
interface LocalState { interface LocalState {
creatingUser: User; creatingUser: User;
...@@ -225,46 +225,33 @@ const MemberSection = observer(() => { ...@@ -225,46 +225,33 @@ const MemberSection = observer(() => {
{currentUser?.name === user.name ? ( {currentUser?.name === user.name ? (
<span>{t("common.yourself")}</span> <span>{t("common.yourself")}</span>
) : ( ) : (
<Popover> <DropdownMenu>
<PopoverTrigger asChild> <DropdownMenuTrigger asChild>
<button className="flex items-center justify-center p-1 hover:bg-muted rounded"> <Button variant="outline">
<MoreVerticalIcon className="w-4 h-auto" /> <MoreVerticalIcon className="w-4 h-auto" />
</button> </Button>
</PopoverTrigger> </DropdownMenuTrigger>
<PopoverContent align="end" sideOffset={2}> <DropdownMenuContent align="end" sideOffset={2}>
<div className="flex flex-col gap-0.5 text-sm"> <DropdownMenuItem onClick={() => showCreateUserDialog(user, () => fetchUsers())}>
<button
onClick={() => showCreateUserDialog(user, () => fetchUsers())}
className="flex items-center gap-2 px-2 py-1 text-left hover:bg-muted outline-none rounded"
>
{t("common.update")} {t("common.update")}
</button> </DropdownMenuItem>
{user.state === State.NORMAL ? ( {user.state === State.NORMAL ? (
<button <DropdownMenuItem onClick={() => handleArchiveUserClick(user)}>
onClick={() => handleArchiveUserClick(user)}
className="flex items-center gap-2 px-2 py-1 text-left hover:bg-muted outline-none rounded"
>
{t("setting.member-section.archive-member")} {t("setting.member-section.archive-member")}
</button> </DropdownMenuItem>
) : ( ) : (
<> <>
<button <DropdownMenuItem onClick={() => handleRestoreUserClick(user)}>{t("common.restore")}</DropdownMenuItem>
onClick={() => handleRestoreUserClick(user)} <DropdownMenuItem
className="flex items-center gap-2 px-2 py-1 text-left hover:bg-muted outline-none rounded"
>
{t("common.restore")}
</button>
<button
onClick={() => handleDeleteUserClick(user)} onClick={() => handleDeleteUserClick(user)}
className="flex items-center gap-2 px-2 py-1 text-left text-destructive hover:bg-muted outline-none rounded" className="text-destructive focus:text-destructive"
> >
{t("setting.member-section.delete-member")} {t("setting.member-section.delete-member")}
</button> </DropdownMenuItem>
</> </>
)} )}
</div> </DropdownMenuContent>
</PopoverContent> </DropdownMenu>
</Popover>
)} )}
</td> </td>
</tr> </tr>
......
...@@ -124,7 +124,7 @@ const MemoRelatedSettings = observer(() => { ...@@ -124,7 +124,7 @@ const MemoRelatedSettings = observer(() => {
<div className="mt-2 w-full flex flex-row flex-wrap gap-1"> <div className="mt-2 w-full flex flex-row flex-wrap gap-1">
{memoRelatedSetting.reactions.map((reactionType) => { {memoRelatedSetting.reactions.map((reactionType) => {
return ( return (
<Badge key={reactionType} variant="outline" className="h-9 flex items-center gap-1"> <Badge key={reactionType} variant="outline" className="flex items-center gap-1">
{reactionType} {reactionType}
<X <X
className="w-3 h-3 cursor-pointer hover:text-destructive" className="w-3 h-3 cursor-pointer hover:text-destructive"
...@@ -155,7 +155,7 @@ const MemoRelatedSettings = observer(() => { ...@@ -155,7 +155,7 @@ const MemoRelatedSettings = observer(() => {
<div className="mt-2 w-full flex flex-row flex-wrap gap-1"> <div className="mt-2 w-full flex flex-row flex-wrap gap-1">
{memoRelatedSetting.nsfwTags.map((nsfwTag) => { {memoRelatedSetting.nsfwTags.map((nsfwTag) => {
return ( return (
<Badge key={nsfwTag} variant="outline" className="h-9 flex items-center gap-1"> <Badge key={nsfwTag} variant="outline" className="flex items-center gap-1">
{nsfwTag} {nsfwTag}
<X <X
className="w-3 h-3 cursor-pointer hover:text-destructive" className="w-3 h-3 cursor-pointer hover:text-destructive"
......
...@@ -5,7 +5,7 @@ import { useTranslate } from "@/utils/i18n"; ...@@ -5,7 +5,7 @@ import { useTranslate } from "@/utils/i18n";
import showChangeMemberPasswordDialog from "../ChangeMemberPasswordDialog"; import showChangeMemberPasswordDialog from "../ChangeMemberPasswordDialog";
import showUpdateAccountDialog from "../UpdateAccountDialog"; import showUpdateAccountDialog from "../UpdateAccountDialog";
import UserAvatar from "../UserAvatar"; import UserAvatar from "../UserAvatar";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../ui/dropdown-menu";
import AccessTokenSection from "./AccessTokenSection"; import AccessTokenSection from "./AccessTokenSection";
import UserSessionsSection from "./UserSessionsSection"; import UserSessionsSection from "./UserSessionsSection";
...@@ -31,21 +31,18 @@ const MyAccountSection = () => { ...@@ -31,21 +31,18 @@ const MyAccountSection = () => {
<PenLineIcon className="w-4 h-4 mx-auto mr-1" /> <PenLineIcon className="w-4 h-4 mx-auto mr-1" />
{t("common.edit")} {t("common.edit")}
</Button> </Button>
<Popover> <DropdownMenu>
<PopoverTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="outline"> <Button variant="outline">
<MoreVerticalIcon className="w-4 h-4 mx-auto" /> <MoreVerticalIcon className="w-4 h-4 mx-auto" />
</Button> </Button>
</PopoverTrigger> </DropdownMenuTrigger>
<PopoverContent align="start" className="text-sm p-1"> <DropdownMenuContent align="start">
<button <DropdownMenuItem onClick={() => showChangeMemberPasswordDialog(user)}>
onClick={() => showChangeMemberPasswordDialog(user)}
className="w-full flex items-center gap-2 px-2 py-1 text-left text-sm hover:bg-muted rounded-md"
>
{t("setting.account-section.change-password")} {t("setting.account-section.change-password")}
</button> </DropdownMenuItem>
</PopoverContent> </DropdownMenuContent>
</Popover> </DropdownMenu>
</div> </div>
<UserSessionsSection /> <UserSessionsSection />
......
...@@ -3,13 +3,13 @@ import { useEffect, useState } from "react"; ...@@ -3,13 +3,13 @@ import { useEffect, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { identityProviderServiceClient } from "@/grpcweb"; import { identityProviderServiceClient } from "@/grpcweb";
import { IdentityProvider } from "@/types/proto/api/v1/idp_service"; import { IdentityProvider } from "@/types/proto/api/v1/idp_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import showCreateIdentityProviderDialog from "../CreateIdentityProviderDialog"; import showCreateIdentityProviderDialog from "../CreateIdentityProviderDialog";
import LearnMore from "../LearnMore"; import LearnMore from "../LearnMore";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
const SSOSection = () => { const SSOSection = () => {
const t = useTranslate(); const t = useTranslate();
...@@ -57,38 +57,28 @@ const SSOSection = () => { ...@@ -57,38 +57,28 @@ const SSOSection = () => {
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
<p className="ml-2"> <p className="ml-2">
{identityProvider.title} {identityProvider.title}
<span className="text-sm ml-1 opacity-40">({identityProvider.type})</span> <span className="text-sm ml-1 text-muted-foreground">({identityProvider.type})</span>
</p> </p>
</div> </div>
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
<Popover> <DropdownMenu>
<PopoverTrigger asChild> <DropdownMenuTrigger asChild>
<button className="flex items-center justify-center p-1 hover:bg-popover rounded"> <Button variant="outline">
<MoreVerticalIcon className="w-4 h-auto" /> <MoreVerticalIcon className="w-4 h-auto" />
</button> </Button>
</PopoverTrigger> </DropdownMenuTrigger>
<PopoverContent align="end" sideOffset={2}> <DropdownMenuContent align="end" sideOffset={2}>
<div className="flex flex-col gap-0.5 text-sm"> <DropdownMenuItem onClick={() => showCreateIdentityProviderDialog(identityProvider, fetchIdentityProviderList)}>
<button
onClick={() => showCreateIdentityProviderDialog(identityProvider, fetchIdentityProviderList)}
className="flex items-center gap-2 px-2 py-1 text-left text-foreground hover:bg-popover outline-none rounded"
>
{t("common.edit")} {t("common.edit")}
</button> </DropdownMenuItem>
<button <DropdownMenuItem onClick={() => handleDeleteIdentityProvider(identityProvider)}>{t("common.delete")}</DropdownMenuItem>
onClick={() => handleDeleteIdentityProvider(identityProvider)} </DropdownMenuContent>
className="flex items-center gap-2 px-2 py-1 text-left text-destructive hover:bg-popover outline-none rounded" </DropdownMenu>
>
{t("common.delete")}
</button>
</div>
</PopoverContent>
</Popover>
</div> </div>
</div> </div>
))} ))}
{identityProviderList.length === 0 && ( {identityProviderList.length === 0 && (
<div className="w-full mt-2 text-sm border-border opacity-60 flex flex-row items-center justify-between"> <div className="w-full mt-2 text-sm border-border text-muted-foreground flex flex-row items-center justify-between">
<p className="">{t("setting.sso-section.no-sso-found")}</p> <p className="">{t("setting.sso-section.no-sso-found")}</p>
</div> </div>
)} )}
......
...@@ -8,9 +8,9 @@ interface Props { ...@@ -8,9 +8,9 @@ interface Props {
const UserAvatar = (props: Props) => { const UserAvatar = (props: Props) => {
const { avatarUrl, className } = props; const { avatarUrl, className } = props;
return ( return (
<div className={cn(`w-8 h-8 overflow-clip rounded-xl`, className)}> <div className={cn(`w-8 h-8 overflow-clip rounded-xl border border-border`, className)}>
<img <img
className="w-full h-auto shadow min-w-full min-h-full object-cover opacity-80" className="w-full h-auto shadow min-w-full min-h-full object-cover"
src={avatarUrl || "/full-logo.webp"} src={avatarUrl || "/full-logo.webp"}
decoding="async" decoding="async"
loading="lazy" loading="lazy"
......
...@@ -6,7 +6,7 @@ import { cn } from "@/lib/utils"; ...@@ -6,7 +6,7 @@ import { cn } from "@/lib/utils";
import { Routes } from "@/router"; import { Routes } from "@/router";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import UserAvatar from "./UserAvatar"; import UserAvatar from "./UserAvatar";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "./ui/dropdown-menu";
interface Props { interface Props {
collapsed?: boolean; collapsed?: boolean;
...@@ -24,16 +24,13 @@ const UserBanner = (props: Props) => { ...@@ -24,16 +24,13 @@ const UserBanner = (props: Props) => {
}; };
return ( return (
<div className="relative w-full h-auto px-1 shrink-0"> <DropdownMenu>
<Popover> <DropdownMenuTrigger asChild disabled={!currentUser}>
<PopoverTrigger asChild disabled={!currentUser}> <div className={cn("w-auto flex flex-row justify-start items-center cursor-pointer text-foreground", collapsed ? "px-1" : "px-3")}>
<div
className={cn("w-auto flex flex-row justify-start items-center cursor-pointer text-foreground", collapsed ? "px-1" : "px-3")}
>
{currentUser.avatarUrl ? ( {currentUser.avatarUrl ? (
<UserAvatar className="shrink-0" avatarUrl={currentUser.avatarUrl} /> <UserAvatar className="shrink-0" avatarUrl={currentUser.avatarUrl} />
) : ( ) : (
<User2Icon className="w-6 mx-auto h-auto opacity-60" /> <User2Icon className="w-6 mx-auto h-auto text-muted-foreground" />
)} )}
{!collapsed && ( {!collapsed && (
<span className="ml-2 text-lg font-medium text-foreground grow truncate"> <span className="ml-2 text-lg font-medium text-foreground grow truncate">
...@@ -41,48 +38,30 @@ const UserBanner = (props: Props) => { ...@@ -41,48 +38,30 @@ const UserBanner = (props: Props) => {
</span> </span>
)} )}
</div> </div>
</PopoverTrigger> </DropdownMenuTrigger>
<PopoverContent align="start" className="p-1" style={{ zIndex: "9999" }}> <DropdownMenuContent align="start">
<div className="flex flex-col text-sm gap-0.5"> <DropdownMenuItem onClick={() => navigateTo(`/u/${encodeURIComponent(currentUser.username)}`)}>
<button <SquareUserIcon className="w-4 h-auto text-muted-foreground" />
onClick={() => navigateTo(`/u/${encodeURIComponent(currentUser.username)}`)}
className="flex items-center gap-2 px-2 py-1 text-left text-foreground hover:bg-muted outline-none rounded"
>
<SquareUserIcon className="w-4 h-auto opacity-60" />
<span className="truncate">{t("common.profile")}</span> <span className="truncate">{t("common.profile")}</span>
</button> </DropdownMenuItem>
<button <DropdownMenuItem onClick={() => navigateTo(Routes.ARCHIVED)}>
onClick={() => navigateTo(Routes.ARCHIVED)} <ArchiveIcon className="w-4 h-auto text-muted-foreground" />
className="flex items-center gap-2 px-2 py-1 text-left text-foreground hover:bg-muted outline-none rounded"
>
<ArchiveIcon className="w-4 h-auto opacity-60" />
<span className="truncate">{t("common.archived")}</span> <span className="truncate">{t("common.archived")}</span>
</button> </DropdownMenuItem>
<button <DropdownMenuItem onClick={() => navigateTo(Routes.INBOX)}>
onClick={() => navigateTo(Routes.INBOX)} <BellIcon className="w-4 h-auto text-muted-foreground" />
className="flex items-center gap-2 px-2 py-1 text-left text-foreground hover:bg-muted outline-none rounded"
>
<BellIcon className="w-4 h-auto opacity-60" />
<span className="truncate">{t("common.inbox")}</span> <span className="truncate">{t("common.inbox")}</span>
</button> </DropdownMenuItem>
<button <DropdownMenuItem onClick={() => navigateTo(Routes.SETTING)}>
onClick={() => navigateTo(Routes.SETTING)} <SettingsIcon className="w-4 h-auto text-muted-foreground" />
className="flex items-center gap-2 px-2 py-1 text-left text-foreground hover:bg-muted outline-none rounded"
>
<SettingsIcon className="w-4 h-auto opacity-60" />
<span className="truncate">{t("common.settings")}</span> <span className="truncate">{t("common.settings")}</span>
</button> </DropdownMenuItem>
<button <DropdownMenuItem onClick={handleSignOut}>
onClick={handleSignOut} <LogOutIcon className="w-4 h-auto text-muted-foreground" />
className="flex items-center gap-2 px-2 py-1 text-left text-foreground hover:bg-muted outline-none rounded"
>
<LogOutIcon className="w-4 h-auto opacity-60" />
<span className="truncate">{t("common.sign-out")}</span> <span className="truncate">{t("common.sign-out")}</span>
</button> </DropdownMenuItem>
</div> </DropdownMenuContent>
</PopoverContent> </DropdownMenu>
</Popover>
</div>
); );
}; };
......
...@@ -12,7 +12,7 @@ const buttonVariants = cva( ...@@ -12,7 +12,7 @@ const buttonVariants = cva(
destructive: destructive:
"bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", "bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline: outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-border dark:hover:bg-input/50",
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline", link: "text-primary underline-offset-4 hover:underline",
......
...@@ -8,7 +8,7 @@ function Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxP ...@@ -8,7 +8,7 @@ function Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxP
<CheckboxPrimitive.Root <CheckboxPrimitive.Root
data-slot="checkbox" data-slot="checkbox"
className={cn( className={cn(
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", "peer border-border dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className, className,
)} )}
{...props} {...props}
......
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
function DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
}
function DropdownMenuPortal({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
}
function DropdownMenuTrigger({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
return <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;
}
function DropdownMenuContent({ className, sideOffset = 4, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
return (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
data-slot="dropdown-menu-content"
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 z-[60] max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
className,
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
);
}
function DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
}
function DropdownMenuItem({
className,
inset,
variant = "default",
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
variant?: "default" | "destructive";
}) {
return (
<DropdownMenuPrimitive.Item
data-slot="dropdown-menu-item"
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
);
}
function DropdownMenuCheckboxItem({
className,
children,
checked,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
return (
<DropdownMenuPrimitive.CheckboxItem
data-slot="dropdown-menu-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
checked={checked}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
);
}
function DropdownMenuRadioGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />;
}
function DropdownMenuRadioItem({ className, children, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
return (
<DropdownMenuPrimitive.RadioItem
data-slot="dropdown-menu-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CircleIcon className="size-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
);
}
function DropdownMenuLabel({
className,
inset,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean;
}) {
return (
<DropdownMenuPrimitive.Label
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn("px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className)}
{...props}
/>
);
}
function DropdownMenuSeparator({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
return (
<DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props}
/>
);
}
function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="dropdown-menu-shortcut"
className={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
{...props}
/>
);
}
function DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
}
function DropdownMenuSubTrigger({
className,
inset,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean;
}) {
return (
<DropdownMenuPrimitive.SubTrigger
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
className,
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto size-4" />
</DropdownMenuPrimitive.SubTrigger>
);
}
function DropdownMenuSubContent({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
return (
<DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 z-[60] min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
className,
)}
{...props}
/>
);
}
export {
DropdownMenu,
DropdownMenuPortal,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
};
...@@ -7,7 +7,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) { ...@@ -7,7 +7,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
type={type} type={type}
data-slot="input" data-slot="input"
className={cn( className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-8 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-border flex h-8 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className, className,
......
...@@ -18,7 +18,7 @@ function PopoverContent({ className, align = "center", sideOffset = 4, ...props ...@@ -18,7 +18,7 @@ function PopoverContent({ className, align = "center", sideOffset = 4, ...props
align={align} align={align}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 z-50 w-auto origin-(--radix-popover-content-transform-origin) rounded-md border p-1 shadow-md outline-hidden", "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 z-[60] w-auto origin-(--radix-popover-content-transform-origin) rounded-md border p-1 shadow-md outline-hidden",
className, className,
)} )}
{...props} {...props}
......
...@@ -12,7 +12,7 @@ function RadioGroupItem({ className, ...props }: React.ComponentProps<typeof Rad ...@@ -12,7 +12,7 @@ function RadioGroupItem({ className, ...props }: React.ComponentProps<typeof Rad
<RadioGroupPrimitive.Item <RadioGroupPrimitive.Item
data-slot="radio-group-item" data-slot="radio-group-item"
className={cn( className={cn(
"border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", "border-border text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className, className,
)} )}
{...props} {...props}
......
...@@ -28,7 +28,7 @@ function SelectTrigger({ ...@@ -28,7 +28,7 @@ function SelectTrigger({
data-slot="select-trigger" data-slot="select-trigger"
data-size={size} data-size={size}
className={cn( className={cn(
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "border-border data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-2 py-1 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-8 data-[size=sm]:h-7 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className, className,
)} )}
{...props} {...props}
...@@ -47,7 +47,7 @@ function SelectContent({ className, children, position = "popper", ...props }: R ...@@ -47,7 +47,7 @@ function SelectContent({ className, children, position = "popper", ...props }: R
<SelectPrimitive.Content <SelectPrimitive.Content
data-slot="select-content" data-slot="select-content"
className={cn( className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md", "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 relative z-[60] max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
position === "popper" && position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className, className,
...@@ -72,7 +72,11 @@ function SelectContent({ className, children, position = "popper", ...props }: R ...@@ -72,7 +72,11 @@ function SelectContent({ className, children, position = "popper", ...props }: R
function SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) { function SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) {
return ( return (
<SelectPrimitive.Label data-slot="select-label" className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)} {...props} /> <SelectPrimitive.Label
data-slot="select-label"
className={cn("text-muted-foreground px-2 py-1 text-xs select-none", className)}
{...props}
/>
); );
} }
......
...@@ -6,7 +6,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { ...@@ -6,7 +6,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
<textarea <textarea
data-slot="textarea" data-slot="textarea"
className={cn( className={cn(
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "border-border placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className, className,
)} )}
{...props} {...props}
......
...@@ -25,13 +25,13 @@ function TooltipContent({ className, sideOffset = 0, children, ...props }: React ...@@ -25,13 +25,13 @@ function TooltipContent({ className, sideOffset = 0, children, ...props }: React
data-slot="tooltip-content" data-slot="tooltip-content"
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 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 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance", "bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 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 z-[70] w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
className, className,
)} )}
{...props} {...props}
> >
{children} {children}
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" /> <TooltipPrimitive.Arrow className="bg-primary fill-primary z-[70] size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
</TooltipPrimitive.Content> </TooltipPrimitive.Content>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
); );
......
...@@ -16,7 +16,7 @@ const HomeLayout = observer(() => { ...@@ -16,7 +16,7 @@ const HomeLayout = observer(() => {
</MobileHeader> </MobileHeader>
)} )}
{md && ( {md && (
<div className={cn("fixed top-0 left-16 shrink-0 h-svh transition-all", "border-x border-border", lg ? "w-72" : "w-56")}> <div className={cn("fixed top-0 left-16 shrink-0 h-svh transition-all", "border-r border-border", lg ? "w-72" : "w-56")}>
<HomeSidebar className={cn("px-3 py-6")} /> <HomeSidebar className={cn("px-3 py-6")} />
</div> </div>
)} )}
......
...@@ -48,7 +48,13 @@ const RootLayout = observer(() => { ...@@ -48,7 +48,13 @@ const RootLayout = observer(() => {
) : ( ) : (
<div className="w-full min-h-full flex flex-row justify-center items-start sm:pl-16"> <div className="w-full min-h-full flex flex-row justify-center items-start sm:pl-16">
{sm && ( {sm && (
<div className={cn("group flex flex-col justify-start items-start fixed top-0 left-0 select-none h-full bg-sidebar", "w-16 px-2")}> <div
className={cn(
"group flex flex-col justify-start items-start fixed top-0 left-0 select-none h-full bg-sidebar",
"w-16 px-2",
"border-r border-border",
)}
>
<Navigation collapsed={true} /> <Navigation collapsed={true} />
</div> </div>
)} )}
......
...@@ -55,6 +55,7 @@ const Home = observer(() => { ...@@ -55,6 +55,7 @@ const Home = observer(() => {
}, [user, memoFilterStore.filters, viewStore.state.orderByTimeAsc]); }, [user, memoFilterStore.filters, viewStore.state.orderByTimeAsc]);
return ( return (
<div className="w-full min-h-full bg-background text-foreground">
<PagedMemoList <PagedMemoList
renderer={(memo: Memo) => <MemoView key={`${memo.name}-${memo.displayTime}`} memo={memo} showVisibility showPinned compact />} renderer={(memo: Memo) => <MemoView key={`${memo.name}-${memo.displayTime}`} memo={memo} showVisibility showPinned compact />}
listSort={(memos: Memo[]) => listSort={(memos: Memo[]) =>
...@@ -72,6 +73,7 @@ const Home = observer(() => { ...@@ -72,6 +73,7 @@ const Home = observer(() => {
filter={selectedShortcut?.filter || ""} filter={selectedShortcut?.filter || ""}
oldFilter={memoListFilter} oldFilter={memoListFilter}
/> />
</div>
); );
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment