componenets standardization 1
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import Button from "../ui/button/Button";
|
||||
|
||||
const ChartTab: React.FC = () => {
|
||||
const [selected, setSelected] = useState<
|
||||
@@ -12,32 +13,35 @@ const ChartTab: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-0.5 rounded-lg bg-gray-100 p-0.5 dark:bg-gray-900">
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => setSelected("optionOne")}
|
||||
className={`px-3 py-2 font-medium w-full rounded-md text-theme-sm hover:text-gray-900 dark:hover:text-white ${getButtonClass(
|
||||
"optionOne"
|
||||
)}`}
|
||||
>
|
||||
Monthly
|
||||
</button>
|
||||
</Button>
|
||||
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => setSelected("optionTwo")}
|
||||
className={`px-3 py-2 font-medium w-full rounded-md text-theme-sm hover:text-gray-900 dark:hover:text-white ${getButtonClass(
|
||||
"optionTwo"
|
||||
)}`}
|
||||
>
|
||||
Quarterly
|
||||
</button>
|
||||
</Button>
|
||||
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => setSelected("optionThree")}
|
||||
className={`px-3 py-2 font-medium w-full rounded-md text-theme-sm hover:text-gray-900 dark:hover:text-white ${getButtonClass(
|
||||
"optionThree"
|
||||
)}`}
|
||||
>
|
||||
Annually
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import Badge from '../ui/badge/Badge';
|
||||
import Button from '../ui/button/Button';
|
||||
|
||||
export interface ContentImageData {
|
||||
id?: number;
|
||||
@@ -73,12 +74,15 @@ export default function ContentImageCell({ image, maxPromptLength = 100, showPro
|
||||
<p className="text-gray-700 dark:text-gray-300">
|
||||
{displayPrompt}
|
||||
{shouldTruncate && (
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
tone="brand"
|
||||
size="xs"
|
||||
onClick={() => setShowFullPrompt(!showFullPrompt)}
|
||||
className="ml-1 text-brand-500 hover:text-brand-600 text-xs"
|
||||
className="ml-1 p-0 h-auto text-xs"
|
||||
>
|
||||
{showFullPrompt ? 'Show less' : 'Show more'}
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
import { Modal } from '../ui/modal';
|
||||
import { CloseIcon } from '../../icons';
|
||||
import Button from '../ui/button/Button';
|
||||
import IconButton from '../ui/button/IconButton';
|
||||
|
||||
interface ContentViewerModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -30,12 +32,14 @@ export default function ContentViewerModal({
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
{title}
|
||||
</h2>
|
||||
<button
|
||||
<IconButton
|
||||
icon={<CloseIcon className="w-6 h-6" />}
|
||||
variant="ghost"
|
||||
tone="neutral"
|
||||
size="sm"
|
||||
title="Close"
|
||||
onClick={onClose}
|
||||
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors"
|
||||
>
|
||||
<CloseIcon className="w-6 h-6" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
@@ -48,12 +52,14 @@ export default function ContentViewerModal({
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex justify-end gap-3 px-6 py-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<button
|
||||
<Button
|
||||
variant="outline"
|
||||
tone="neutral"
|
||||
size="sm"
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
@@ -3,6 +3,8 @@ import { Modal } from '../ui/modal';
|
||||
import Button from '../ui/button/Button';
|
||||
import SelectDropdown from '../form/SelectDropdown';
|
||||
import Label from '../form/Label';
|
||||
import InputField from '../form/input/InputField';
|
||||
import TextArea from '../form/input/TextArea';
|
||||
|
||||
export interface FormField {
|
||||
key: string;
|
||||
@@ -69,17 +71,15 @@ export default function FormModal({
|
||||
<>
|
||||
{fields.find(f => f.key === 'keyword') && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
<Label className="mb-2">
|
||||
{fields.find(f => f.key === 'keyword')!.label}
|
||||
{fields.find(f => f.key === 'keyword')!.required && <span className="text-error-500 ml-1">*</span>}
|
||||
</label>
|
||||
<input
|
||||
</Label>
|
||||
<InputField
|
||||
type="text"
|
||||
className="h-9 w-full rounded-lg border border-gray-300 bg-transparent px-3 py-2 text-sm shadow-theme-xs text-gray-800 placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
value={fields.find(f => f.key === 'keyword')!.value || ''}
|
||||
onChange={(e) => fields.find(f => f.key === 'keyword')!.onChange(e.target.value)}
|
||||
placeholder={fields.find(f => f.key === 'keyword')!.placeholder}
|
||||
required={fields.find(f => f.key === 'keyword')!.required}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -87,20 +87,18 @@ export default function FormModal({
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{fields.find(f => f.key === 'volume') && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
<Label className="mb-2">
|
||||
{fields.find(f => f.key === 'volume')!.label}
|
||||
{fields.find(f => f.key === 'volume')!.required && <span className="text-error-500 ml-1">*</span>}
|
||||
</label>
|
||||
<input
|
||||
</Label>
|
||||
<InputField
|
||||
type="number"
|
||||
className="h-9 w-full rounded-lg border border-gray-300 bg-transparent px-3 py-2 text-sm shadow-theme-xs text-gray-800 placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
value={fields.find(f => f.key === 'volume')!.value || ''}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value === '' ? '' : parseInt(e.target.value) || 0;
|
||||
fields.find(f => f.key === 'volume')!.onChange(value);
|
||||
}}
|
||||
placeholder={fields.find(f => f.key === 'volume')!.placeholder}
|
||||
required={fields.find(f => f.key === 'volume')!.required}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -121,18 +119,14 @@ export default function FormModal({
|
||||
className="w-full"
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
<InputField
|
||||
type="number"
|
||||
className="h-9 w-full rounded-lg border border-gray-300 bg-transparent px-3 py-2 text-sm shadow-theme-xs text-gray-800 placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
value={difficultyField.value || ''}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value === '' ? '' : parseInt(e.target.value) || 0;
|
||||
difficultyField.onChange(value);
|
||||
}}
|
||||
placeholder={difficultyField.placeholder}
|
||||
required={difficultyField.required}
|
||||
min={difficultyField.min}
|
||||
max={difficultyField.max}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -161,36 +155,30 @@ export default function FormModal({
|
||||
if (field.type === 'textarea') {
|
||||
return (
|
||||
<div key={`${field.key}-${idx}`}>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
<Label className="mb-2">
|
||||
{field.label}
|
||||
{field.required && <span className="text-error-500 ml-1">*</span>}
|
||||
</label>
|
||||
<textarea
|
||||
</Label>
|
||||
<TextArea
|
||||
rows={field.rows || 4}
|
||||
className="w-full rounded-lg border border-gray-300 bg-transparent px-3 py-2 text-sm shadow-theme-xs text-gray-800 placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
value={field.value || ''}
|
||||
onChange={(e) => field.onChange(e.target.value)}
|
||||
onChange={(val) => field.onChange(val)}
|
||||
placeholder={field.placeholder}
|
||||
required={field.required}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div key={`${field.key}-${idx}`}>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
<Label className="mb-2">
|
||||
{field.label}
|
||||
{field.required && <span className="text-error-500 ml-1">*</span>}
|
||||
</label>
|
||||
<input
|
||||
</Label>
|
||||
<InputField
|
||||
type={field.type}
|
||||
className="h-9 w-full rounded-lg border border-gray-300 bg-transparent px-3 py-2 text-sm shadow-theme-xs text-gray-800 placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
value={field.value || ''}
|
||||
onChange={(e) => field.onChange(e.target.value)}
|
||||
placeholder={field.placeholder}
|
||||
required={field.required}
|
||||
min={field.min}
|
||||
max={field.max}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useErrorHandler } from '../../hooks/useErrorHandler';
|
||||
import IconButton from '../ui/button/IconButton';
|
||||
import Button from '../ui/button/Button';
|
||||
|
||||
export default function GlobalErrorDisplay() {
|
||||
const { errors, clearError, clearAllErrors } = useErrorHandler('GlobalErrorDisplay');
|
||||
@@ -42,23 +44,27 @@ export default function GlobalErrorDisplay() {
|
||||
</details>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
<IconButton
|
||||
icon={<span className="text-xl leading-none">×</span>}
|
||||
onClick={() => clearError(index)}
|
||||
className="text-error-600 dark:text-error-400 hover:text-error-800 dark:hover:text-error-200 text-xl leading-none"
|
||||
variant="ghost"
|
||||
tone="danger"
|
||||
size="sm"
|
||||
aria-label="Dismiss error"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{errors.length > 1 && (
|
||||
<button
|
||||
<Button
|
||||
onClick={clearAllErrors}
|
||||
className="w-full px-3 py-2 text-xs bg-error-600 text-white rounded hover:bg-error-700"
|
||||
variant="primary"
|
||||
tone="danger"
|
||||
size="sm"
|
||||
fullWidth
|
||||
>
|
||||
Clear All Errors
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { ReactNode, useState, useEffect } from 'react';
|
||||
import Button from '../ui/button/Button';
|
||||
import TextArea from '../form/input/TextArea';
|
||||
import Select from '../form/Select';
|
||||
import Label from '../form/Label';
|
||||
import { useToast } from '../ui/toast/ToastContainer';
|
||||
import { fetchAPI } from '../../services/api';
|
||||
|
||||
@@ -292,14 +295,13 @@ export default function ImageGenerationCard({
|
||||
|
||||
{/* Prompt Description - Full Width */}
|
||||
<div>
|
||||
<label className="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<Label className="mb-2">
|
||||
Prompt Description *
|
||||
</label>
|
||||
<textarea
|
||||
</Label>
|
||||
<TextArea
|
||||
value={prompt}
|
||||
onChange={(e) => setPrompt(e.target.value)}
|
||||
onChange={(val) => setPrompt(val)}
|
||||
rows={6}
|
||||
className="w-full rounded-lg border border-gray-300 px-4 py-3 text-sm focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-500/20 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
|
||||
placeholder="Describe the visual elements, style, mood, and composition you want in the image..."
|
||||
/>
|
||||
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
@@ -309,14 +311,13 @@ export default function ImageGenerationCard({
|
||||
|
||||
{/* Negative Prompt - Small */}
|
||||
<div>
|
||||
<label className="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<Label className="mb-2">
|
||||
Negative Prompt
|
||||
</label>
|
||||
<textarea
|
||||
</Label>
|
||||
<TextArea
|
||||
value={negativePrompt}
|
||||
onChange={(e) => setNegativePrompt(e.target.value)}
|
||||
onChange={(val) => setNegativePrompt(val)}
|
||||
rows={2}
|
||||
className="w-full rounded-lg border border-gray-300 px-4 py-3 text-sm focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-500/20 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
|
||||
placeholder="Describe what you DON'T want in the image..."
|
||||
/>
|
||||
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
@@ -328,56 +329,38 @@ export default function ImageGenerationCard({
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{/* Image Type */}
|
||||
<div>
|
||||
<label className="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<Label className="mb-2">
|
||||
Image Type
|
||||
</label>
|
||||
<select
|
||||
value={imageType}
|
||||
onChange={(e) => setImageType(e.target.value)}
|
||||
className="w-full rounded-lg border border-gray-300 px-4 py-2.5 text-sm focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-500/20 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
|
||||
>
|
||||
{typeOptions.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Label>
|
||||
<Select
|
||||
options={typeOptions}
|
||||
defaultValue={imageType}
|
||||
onChange={(val) => setImageType(val)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Image Size */}
|
||||
<div>
|
||||
<label className="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<Label className="mb-2">
|
||||
Image Size
|
||||
</label>
|
||||
<select
|
||||
value={imageSize}
|
||||
onChange={(e) => setImageSize(e.target.value)}
|
||||
className="w-full rounded-lg border border-gray-300 px-4 py-2.5 text-sm focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-500/20 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
|
||||
>
|
||||
{sizeOptions.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Label>
|
||||
<Select
|
||||
options={sizeOptions}
|
||||
defaultValue={imageSize}
|
||||
onChange={(val) => setImageSize(val)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Image Format */}
|
||||
<div>
|
||||
<label className="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<Label className="mb-2">
|
||||
Image Format
|
||||
</label>
|
||||
<select
|
||||
value={imageFormat}
|
||||
onChange={(e) => setImageFormat(e.target.value)}
|
||||
className="w-full rounded-lg border border-gray-300 px-4 py-2.5 text-sm focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-500/20 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
|
||||
>
|
||||
{formatOptions.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Label>
|
||||
<Select
|
||||
options={formatOptions}
|
||||
defaultValue={imageFormat}
|
||||
onChange={(val) => setImageFormat(val)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import React, { useEffect, useState, useRef } from 'react';
|
||||
import { Modal } from '../ui/modal';
|
||||
import { FileIcon, TimeIcon, CheckCircleIcon, ErrorIcon } from '../../icons';
|
||||
import { fetchAPI } from '../../services/api';
|
||||
import Button from '../ui/button/Button';
|
||||
|
||||
export interface ImageQueueItem {
|
||||
imageId: number | null;
|
||||
@@ -593,12 +594,14 @@ export default function ImageQueueModal({
|
||||
{completedCount} completed{failedCount > 0 ? `, ${failedCount} failed` : ''} of {totalImages} total
|
||||
</div>
|
||||
{allDone && (
|
||||
<button
|
||||
<Button
|
||||
variant="primary"
|
||||
tone="brand"
|
||||
size="sm"
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 bg-brand-500 text-white rounded-lg hover:bg-brand-600 transition-colors"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ReactNode, useEffect, useState } from 'react';
|
||||
import Button from '../ui/button/Button';
|
||||
|
||||
interface ImageResultCardProps {
|
||||
title: string;
|
||||
@@ -195,11 +196,13 @@ export default function ImageResultCard({
|
||||
</svg>
|
||||
View Original
|
||||
</a>
|
||||
<button
|
||||
<Button
|
||||
variant="outline"
|
||||
tone="neutral"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(imageData.url);
|
||||
}}
|
||||
className="inline-flex items-center gap-2 rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-800"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -211,12 +214,13 @@ export default function ImageResultCard({
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="mr-2"
|
||||
>
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
||||
</svg>
|
||||
Copy URL
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Modal } from '../ui/modal';
|
||||
import Button from '../ui/button/Button';
|
||||
|
||||
interface SearchModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -87,11 +88,12 @@ export default function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
<Modal isOpen={isOpen} onClose={onClose} className="sm:max-w-lg">
|
||||
<div className="p-0">
|
||||
<div className="relative">
|
||||
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400">
|
||||
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 z-10">
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</span>
|
||||
{/* Using native input for ref and onKeyDown support - styled to match design system */}
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
@@ -99,9 +101,9 @@ export default function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Search pages..."
|
||||
className="w-full pl-12 pr-4 py-4 text-lg border-b border-gray-200 dark:border-gray-700 bg-transparent focus:outline-none dark:text-white"
|
||||
className="h-9 w-full rounded-lg border appearance-none px-3 py-2 text-sm shadow-theme-xs placeholder:text-gray-400 focus:outline-hidden focus:ring-3 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 bg-transparent text-gray-800 border-gray-300 focus:border-brand-300 focus:ring-brand-500/20 dark:border-gray-700 dark:focus:border-brand-800 pl-12 pr-4 py-4 text-lg border-b border-gray-200 dark:border-gray-700 rounded-none border-x-0 border-t-0"
|
||||
/>
|
||||
<span className="absolute right-4 top-1/2 -translate-y-1/2 text-xs text-gray-400 hidden sm:block">
|
||||
<span className="absolute right-4 top-1/2 -translate-y-1/2 text-xs text-gray-400 hidden sm:block z-10">
|
||||
ESC to close
|
||||
</span>
|
||||
</div>
|
||||
@@ -113,10 +115,12 @@ export default function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
</div>
|
||||
) : (
|
||||
filteredResults.map((result, index) => (
|
||||
<button
|
||||
<Button
|
||||
key={result.path}
|
||||
variant="ghost"
|
||||
tone="neutral"
|
||||
onClick={() => handleSelect(result)}
|
||||
className={`w-full px-4 py-3 flex items-center gap-3 text-left transition-colors ${
|
||||
className={`w-full px-4 py-3 flex items-center gap-3 text-left justify-start rounded-none ${
|
||||
index === selectedIndex
|
||||
? 'bg-brand-50 dark:bg-brand-900/20 text-brand-600 dark:text-brand-400'
|
||||
: 'hover:bg-gray-50 dark:hover:bg-gray-800 text-gray-700 dark:text-gray-300'
|
||||
@@ -126,7 +130,7 @@ export default function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
<span className="font-medium">{result.title}</span>
|
||||
</button>
|
||||
</Button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { GridIcon, ListIcon, TableIcon } from "../../icons";
|
||||
import Button from "../ui/button/Button";
|
||||
|
||||
export type ViewType = "table" | "kanban" | "list";
|
||||
|
||||
@@ -23,19 +24,18 @@ const ViewToggle: React.FC<ViewToggleProps> = ({
|
||||
return (
|
||||
<div className={`inline-flex items-center gap-1 rounded-lg bg-gray-100 p-0.5 dark:bg-gray-900 ${className}`}>
|
||||
{views.map((view) => (
|
||||
<button
|
||||
<Button
|
||||
key={view.type}
|
||||
onClick={() => onViewChange(view.type)}
|
||||
className={`inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${
|
||||
currentView === view.type
|
||||
? "bg-white text-gray-900 dark:bg-gray-800 dark:text-white shadow-sm"
|
||||
: "text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
|
||||
}`}
|
||||
variant={currentView === view.type ? "secondary" : "ghost"}
|
||||
tone="neutral"
|
||||
size="sm"
|
||||
className={currentView === view.type ? "shadow-sm" : ""}
|
||||
title={view.label}
|
||||
startIcon={view.icon}
|
||||
>
|
||||
{view.icon}
|
||||
<span className="hidden sm:inline">{view.label}</span>
|
||||
</button>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user