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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ html[data-theme-loaded='true'] * {
border-color 0.25s ease,
box-shadow 0.25s ease;
}
input[type='search']::-webkit-search-cancel-button {
display: none;
}

/* Hide scrollbar but preserve scrolling */
.scrollbar-hide {
Expand Down Expand Up @@ -206,3 +209,9 @@ html[data-theme-loaded='true'] * {
opacity: 1;
}
}

@keyframes skeletonShimmer {
100% {
transform: translateX(100%);
}
}
16 changes: 14 additions & 2 deletions src/layouts/AdminLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react'
import { useEffect, useState } from 'react'

import { Outlet } from 'react-router-dom'

Expand All @@ -17,10 +17,16 @@
QueueListIcon,
UsersIcon,
} from '@heroicons/react/24/outline'
import { Skeleton } from '@/shared/primitives/Skeleton'

const AdminLayout = () => {
const [collapsed, setCollapsed] = useState(false)
const [activeItem, setActiveItem] = useState('Queue')
const [loading, setLoading] = useState(true)

useEffect(() => {
setTimeout(() => setLoading(!loading), 5000)
}, [])

Check warning on line 29 in src/layouts/AdminLayout.tsx

View workflow job for this annotation

GitHub Actions / accessibility

React Hook useEffect has a missing dependency: 'loading'. Either include it or remove the dependency array. You can also do a functional update 'setLoading(l => ...)' if you only need 'loading' in the 'setLoading' call

Check warning on line 29 in src/layouts/AdminLayout.tsx

View workflow job for this annotation

GitHub Actions / validate

React Hook useEffect has a missing dependency: 'loading'. Either include it or remove the dependency array. You can also do a functional update 'setLoading(l => ...)' if you only need 'loading' in the 'setLoading' call

return (
<div className="bg-bg-primary text-text-primary min-h-screen">
Expand Down Expand Up @@ -127,7 +133,13 @@
title="Moderation Queue"
showSearch
searchPlaceholder="Search reports..."
actionsSlot={<AvatarMenu name="Admin Mod" />}
actionsSlot={
loading ? (
<Skeleton variant={'avatar'} className="shimmer" />
) : (
<AvatarMenu name="Admin Mod" />
)
}
/>

<main className="p-6">
Expand Down
19 changes: 9 additions & 10 deletions src/shared/composites/ReportCard/ReportCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export function ReportCard({
isClickable
onToggle={onToggleExpand}
header={
<div className="relative">
<div className="space-y-4">
<div className="flex items-start justify-between gap-4">
<div className="space-y-2">
<div className="flex flex-wrap items-center gap-2">
Expand All @@ -225,30 +225,29 @@ export function ReportCard({

<Badge variant="info">{resolvedReport.targetType}</Badge>
</div>

<div>
{' '}
<span className="text-text-primary text-sm font-medium">
{resolvedReport.reportReason}
</span>
</div>
<div className="text-text-secondary text-xs">
Reported by {resolvedReport.reporter.name} · AI score:{' '}
{resolvedReport.aiConfidenceScore}
</div>
</div>

<div className="w-40">
<div className="flex w-24 shrink-0 flex-col items-center justify-center gap-4 sm:w-40">
<ProgressBar
value={resolvedReport.aiConfidenceScore}
size="sm"
variant="auto"
scoreLabel="AI conf."
/>
<div className="text-text-secondary text-xs">
Reported by {resolvedReport.reporter.name} · AI score:{' '}
{resolvedReport.aiConfidenceScore}
</div>
</div>
</div>
<div
className={`absolute flex w-full items-center justify-center ${isExpanded ? 'top-19' : 'top-23'} text-text-secondary text-xs`}
>

<div className="text-text-secondary flex justify-center text-xs">
<span className="cursor-pointer">
Click to view {isExpanded ? 'report summary' : 'detailed report'}
</span>
Expand Down
24 changes: 18 additions & 6 deletions src/shared/composites/Topbar/Topbar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState } from 'react'

import { MagnifyingGlassIcon } from '@heroicons/react/24/solid'
import { MagnifyingGlassIcon, XMarkIcon } from '@heroicons/react/24/solid'
import { Input } from '@/shared/primitives/Input'

export interface TopbarProps {
Expand All @@ -25,8 +25,6 @@ export function Topbar({
searchSlot,
showSearch,
actionsSlot,

onMenuToggle,
searchPlaceholder,
className = '',
}: TopbarProps) {
Expand All @@ -37,12 +35,12 @@ export function Topbar({
>
{/* Left */}
<div className="flex items-center gap-3 md:min-w-[200px]">
{/* Mobile hamburger */}
<button
{/* In case logo chahiye */}
{/* <button
type="button"
onClick={onMenuToggle}
className="text-text-secondary hover:bg-bg-secondary hover:text-text-primary rounded-md p-2 transition-colors md:hidden"
></button>
></button> */}

{/* Title */}
<h1 className="text-text-primary text-lg font-semibold">{title}</h1>
Expand All @@ -61,6 +59,20 @@ export function Topbar({
}}
placeholder={searchPlaceholder || 'Search...'}
prefixIcon={<MagnifyingGlassIcon className="text-text-tertiary size-4" />}
suffixIcon={
searchQuery ? (
<button
type="button"
aria-label="Clear search"
onClick={() => {
overwriteSearchQuery('')
}}
className="text-text-tertiary hover:text-text-primary cursor-pointer transition-colors"
>
<XMarkIcon className="size-4" />
</button>
) : null
}
/>
)}
</div>
Expand Down
8 changes: 8 additions & 0 deletions src/shared/primitives/Avatar/Avatar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,11 @@ export const BrokenImageFallback: Story = {
src: '/broken-image.png',
},
}

export const Decorative: Story = {
args: {
name: '',

src: 'https://i.pravatar.cc/150?img=12',
},
}
8 changes: 8 additions & 0 deletions src/shared/primitives/Badge/Badge.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ export const Medium: Story = {
}

export const Dot: Story = {
render: (args) => (
<div className="flex items-center gap-2">
<Badge {...args} />

<span className="text-sm">System online</span>
</div>
),

args: {
variant: 'success',
dot: true,
Expand Down
3 changes: 1 addition & 2 deletions src/shared/primitives/BaseCard/BaseCard.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,7 @@ export const NestedBaseCards: Story = {
render: () => (
<BaseCard padding="lg">
<div className="space-y-4">
<p>Parent card</p>

<h2 className="text-text-primary text-sm font-medium">Parent card</h2>
<BaseCard variant="info" padding="sm">
Nested card
</BaseCard>
Expand Down
19 changes: 19 additions & 0 deletions src/shared/primitives/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,22 @@ export const FullWidth: Story = {
fullWidth: true,
},
}
export const Focused: Story = {
args: {
children: 'Focused Button',
},

play: async ({ canvasElement }) => {
const button = canvasElement.querySelector('button')

button?.focus()
},
}

export const IconOnly: Story = {
args: {
'aria-label': 'Close',

children: '×',
},
}
16 changes: 14 additions & 2 deletions src/shared/primitives/Input/Input.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'

import { useState } from 'react'

import { MagnifyingGlassIcon, EyeIcon } from '@heroicons/react/24/outline'
import { MagnifyingGlassIcon, XMarkIcon } from '@heroicons/react/24/outline'

import { Input, type InputProps } from './Input'

Expand Down Expand Up @@ -81,7 +81,7 @@ export const WithIcons: Story = {

prefixIcon: <MagnifyingGlassIcon className="size-4" />,

suffixIcon: <EyeIcon className="size-4" />,
suffixIcon: <XMarkIcon className="size-4" />,
},
}

Expand Down Expand Up @@ -112,3 +112,15 @@ export const Focus: Story = {
input?.focus()
},
}

export const ReadOnly: Story = {
render: (args) => <StatefulInput {...args} />,

args: {
label: 'Read only',

value: 'Readonly value',

isReadOnly: true,
},
}
10 changes: 10 additions & 0 deletions src/shared/primitives/Progressbar/ProgressBar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,13 @@ export const ExtraSmall: Story = {
size: 'xs',
},
}

export const WithAccessibleLabel: Story = {
args: {
value: 0.65,

variant: 'warning',

scoreLabel: 'AI confidence',
},
}
90 changes: 90 additions & 0 deletions src/shared/primitives/Skeleton/Skeleton.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import type { Meta, StoryObj } from '@storybook/react-vite'

import { Skeleton } from './Skeleton'

const meta: Meta<typeof Skeleton> = {
title: 'Shared/Primitives/Skeleton',

component: Skeleton,

tags: ['autodocs'],

parameters: {
layout: 'padded',
},

decorators: [
(Story) => (
<div className="max-w-3xl p-6">
<Story />
</div>
),
],
}

export default meta

type Story = StoryObj<typeof meta>

export const Text: Story = {
args: {
variant: 'text',
lines: 3,
},
}

export const Card: Story = {
args: {
variant: 'card',
},
}

export const Avatar: Story = {
args: {
variant: 'avatar',

width: 56,

height: 56,
},
}

export const Bar: Story = {
args: {
variant: 'bar',

width: '100%',
},
}

export const Custom: Story = {
args: {
variant: 'custom',

width: 240,

height: 80,

className: 'rounded-xl',
},
}

export const CardList: Story = {
render: () => (
<div className="space-y-4">
{Array.from({
length: 3,
}).map((_, index) => (
<Skeleton key={index} variant="card" />
))}
</div>
),
}
export const LoadingRegion: Story = {
render: () => (
<div aria-busy="true" aria-label="Loading reports" className="space-y-4">
<Skeleton variant="card" />
<Skeleton variant="card" />
</div>
),
}
Loading
Loading