chore: upgrade @ciphera-net/ui to v0.0.14 and refactor component imports
This commit is contained in:
@@ -11,9 +11,8 @@ import TopReferrers from '@/components/dashboard/TopReferrers'
|
||||
import Locations from '@/components/dashboard/Locations'
|
||||
import TechSpecs from '@/components/dashboard/TechSpecs'
|
||||
import PerformanceStats from '@/components/dashboard/PerformanceStats'
|
||||
import Select from '@/components/ui/Select'
|
||||
import { Select, DatePicker as DatePickerModal } from '@ciphera-net/ui'
|
||||
import { LightningBoltIcon } from '@radix-ui/react-icons'
|
||||
import DatePickerModal from '@/components/ui/DatePicker'
|
||||
|
||||
// Helper to get date ranges
|
||||
const getDateRange = (days: number) => {
|
||||
|
||||
@@ -8,8 +8,7 @@ import { getStats, getRealtime, getDailyStats, getTopPages, getTopReferrers, get
|
||||
import { formatNumber, formatDuration, getDateRange } from '@/lib/utils/format'
|
||||
import { toast } from 'sonner'
|
||||
import { LoadingOverlay } from '@ciphera-net/ui'
|
||||
import Select from '@/components/ui/Select'
|
||||
import DatePicker from '@/components/ui/DatePicker'
|
||||
import { Select, DatePicker } from '@ciphera-net/ui'
|
||||
import ContentStats from '@/components/dashboard/ContentStats'
|
||||
import TopReferrers from '@/components/dashboard/TopReferrers'
|
||||
import Locations from '@/components/dashboard/Locations'
|
||||
|
||||
@@ -7,7 +7,7 @@ import { toast } from 'sonner'
|
||||
import { LoadingOverlay } from '@ciphera-net/ui'
|
||||
import VerificationModal from '@/components/sites/VerificationModal'
|
||||
import { PasswordInput } from '@ciphera-net/ui'
|
||||
import Select from '@/components/ui/Select'
|
||||
import { Select } from '@ciphera-net/ui'
|
||||
import { APP_URL, API_URL } from '@/lib/api/client'
|
||||
import { generatePrivacySnippet } from '@/lib/utils/privacySnippet'
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
|
||||
@@ -16,7 +16,7 @@ import type { TooltipProps } from 'recharts'
|
||||
import { formatNumber, formatDuration } from '@/lib/utils/format'
|
||||
import { ArrowTopRightIcon, ArrowBottomRightIcon, DownloadIcon, BarChartIcon } from '@radix-ui/react-icons'
|
||||
import { Button } from '@ciphera-net/ui'
|
||||
import { Checkbox } from '@/components/ui/Checkbox'
|
||||
import { Checkbox } from '@ciphera-net/ui'
|
||||
|
||||
const COLORS = {
|
||||
brand: '#FD5E0F',
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useState, useEffect } from 'react'
|
||||
import { motion } from 'framer-motion'
|
||||
import { ChevronDownIcon } from '@radix-ui/react-icons'
|
||||
import { PerformanceStats as Stats, PerformanceByPageStat, getPerformanceByPage } from '@/lib/api/stats'
|
||||
import Select from '@/components/ui/Select'
|
||||
import { Select } from '@ciphera-net/ui'
|
||||
|
||||
interface Props {
|
||||
stats: Stats
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
'use client'
|
||||
|
||||
interface StatsCardProps {
|
||||
title: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export default function StatsCard({ title, value }: StatsCardProps) {
|
||||
return (
|
||||
<div className="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-xl p-6">
|
||||
<div className="text-sm text-neutral-600 dark:text-neutral-400 mb-2">
|
||||
{title}
|
||||
</div>
|
||||
<div className="text-3xl font-bold text-neutral-900 dark:text-white">
|
||||
{value}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import React from 'react';
|
||||
import { CheckIcon } from '@radix-ui/react-icons';
|
||||
|
||||
export interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'> {
|
||||
checked?: boolean;
|
||||
onCheckedChange?: (checked: boolean) => void;
|
||||
label?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
||||
({ className = '', checked, onCheckedChange, label, disabled, ...props }, ref) => {
|
||||
return (
|
||||
<label className={`inline-flex items-center gap-2 cursor-pointer select-none group ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`}>
|
||||
<div className="relative flex items-center justify-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
ref={ref}
|
||||
className="peer sr-only"
|
||||
checked={checked}
|
||||
onChange={(e) => onCheckedChange?.(e.target.checked)}
|
||||
disabled={disabled}
|
||||
{...props}
|
||||
/>
|
||||
<div className={`
|
||||
w-5 h-5 rounded-md border transition-all duration-200 flex items-center justify-center
|
||||
${checked
|
||||
? 'bg-brand-orange border-brand-orange text-white'
|
||||
: 'bg-white dark:bg-neutral-900 border-neutral-300 dark:border-neutral-700 group-hover:border-brand-orange/50'}
|
||||
peer-focus-visible:ring-2 peer-focus-visible:ring-brand-orange/20 peer-focus-visible:ring-offset-2
|
||||
`}>
|
||||
<CheckIcon className={`w-3.5 h-3.5 transition-transform duration-200 ${checked ? 'scale-100' : 'scale-0'}`} />
|
||||
</div>
|
||||
</div>
|
||||
{label && (
|
||||
<span className="text-sm font-medium text-neutral-600 dark:text-neutral-400 group-hover:text-neutral-900 dark:group-hover:text-neutral-200 transition-colors">
|
||||
{label}
|
||||
</span>
|
||||
)}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Checkbox.displayName = 'Checkbox';
|
||||
@@ -1,183 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { ChevronLeftIcon, ChevronRightIcon, Cross2Icon } from '@radix-ui/react-icons'
|
||||
|
||||
interface DateRange {
|
||||
start: string
|
||||
end: string
|
||||
}
|
||||
|
||||
interface DatePickerProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
onApply: (range: DateRange) => void
|
||||
initialRange: DateRange
|
||||
}
|
||||
|
||||
export default function DatePicker({ isOpen, onClose, onApply, initialRange }: DatePickerProps) {
|
||||
const [startDate, setStartDate] = useState<Date>(new Date(initialRange.start))
|
||||
const [endDate, setEndDate] = useState<Date>(new Date(initialRange.end))
|
||||
const [currentMonth, setCurrentMonth] = useState<Date>(new Date(initialRange.end))
|
||||
const [selectingStart, setSelectingStart] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setStartDate(new Date(initialRange.start))
|
||||
setEndDate(new Date(initialRange.end))
|
||||
setCurrentMonth(new Date(initialRange.end))
|
||||
}
|
||||
}, [isOpen, initialRange])
|
||||
|
||||
if (!isOpen) return null
|
||||
|
||||
const getDaysInMonth = (date: Date) => {
|
||||
const year = date.getFullYear()
|
||||
const month = date.getMonth()
|
||||
const days = new Date(year, month + 1, 0).getDate()
|
||||
const firstDay = new Date(year, month, 1).getDay()
|
||||
return { days, firstDay }
|
||||
}
|
||||
|
||||
const { days, firstDay } = getDaysInMonth(currentMonth)
|
||||
|
||||
const handleDateClick = (day: number) => {
|
||||
const clickedDate = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), day)
|
||||
|
||||
if (selectingStart) {
|
||||
setStartDate(clickedDate)
|
||||
// If clicked date is after current end date, reset end date
|
||||
if (clickedDate > endDate) {
|
||||
setEndDate(clickedDate)
|
||||
}
|
||||
setSelectingStart(false)
|
||||
} else {
|
||||
if (clickedDate < startDate) {
|
||||
setStartDate(clickedDate)
|
||||
setSelectingStart(false) // Keep selecting start effectively if they clicked before start
|
||||
} else {
|
||||
setEndDate(clickedDate)
|
||||
setSelectingStart(true) // Reset to start for next interaction or just done
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleMonthChange = (increment: number) => {
|
||||
const newMonth = new Date(currentMonth)
|
||||
newMonth.setMonth(newMonth.getMonth() + increment)
|
||||
setCurrentMonth(newMonth)
|
||||
}
|
||||
|
||||
const formatDate = (date: Date) => date.toISOString().split('T')[0]
|
||||
|
||||
const isSelected = (day: number) => {
|
||||
const date = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), day)
|
||||
return (
|
||||
date.getTime() === startDate.getTime() ||
|
||||
date.getTime() === endDate.getTime()
|
||||
)
|
||||
}
|
||||
|
||||
const isInRange = (day: number) => {
|
||||
const date = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), day)
|
||||
return date > startDate && date < endDate
|
||||
}
|
||||
|
||||
const isToday = (day: number) => {
|
||||
const today = new Date()
|
||||
const date = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), day)
|
||||
return (
|
||||
date.getDate() === today.getDate() &&
|
||||
date.getMonth() === today.getMonth() &&
|
||||
date.getFullYear() === today.getFullYear()
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
|
||||
<div className="bg-white dark:bg-neutral-900 rounded-xl shadow-2xl border border-neutral-200 dark:border-neutral-800 w-full max-w-md p-6 animate-in fade-in zoom-in duration-200">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-lg font-semibold text-neutral-900 dark:text-white">Select Date Range</h2>
|
||||
<button onClick={onClose} className="text-neutral-500 hover:text-neutral-900 dark:hover:text-white transition-colors">
|
||||
<Cross2Icon className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mb-6 flex items-center justify-between bg-neutral-50 dark:bg-neutral-800 p-3 rounded-lg">
|
||||
<button onClick={() => handleMonthChange(-1)} className="p-1 hover:bg-neutral-200 dark:hover:bg-neutral-700 rounded-md transition-colors">
|
||||
<ChevronLeftIcon className="w-5 h-5" />
|
||||
</button>
|
||||
<span className="font-medium text-neutral-900 dark:text-white">
|
||||
{currentMonth.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })}
|
||||
</span>
|
||||
<button onClick={() => handleMonthChange(1)} className="p-1 hover:bg-neutral-200 dark:hover:bg-neutral-700 rounded-md transition-colors">
|
||||
<ChevronRightIcon className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-7 gap-1 mb-2 text-center text-xs font-medium text-neutral-500 uppercase tracking-wider">
|
||||
{['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'].map(day => (
|
||||
<div key={day}>{day}</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-7 gap-1 mb-6">
|
||||
{Array.from({ length: firstDay }).map((_, i) => (
|
||||
<div key={`empty-${i}`} />
|
||||
))}
|
||||
{Array.from({ length: days }).map((_, i) => {
|
||||
const day = i + 1
|
||||
const selected = isSelected(day)
|
||||
const inRange = isInRange(day)
|
||||
const today = isToday(day)
|
||||
|
||||
return (
|
||||
<button
|
||||
key={day}
|
||||
onClick={() => handleDateClick(day)}
|
||||
className={`
|
||||
h-9 w-9 rounded-full text-sm font-medium flex items-center justify-center transition-all
|
||||
${selected
|
||||
? 'bg-brand-orange text-white shadow-md shadow-brand-orange/20'
|
||||
: inRange
|
||||
? 'bg-brand-orange/10 dark:bg-brand-orange/20 text-brand-orange'
|
||||
: 'hover:bg-neutral-100 dark:hover:bg-neutral-800 text-neutral-900 dark:text-white'
|
||||
}
|
||||
${today && !selected && !inRange ? 'ring-1 ring-brand-orange text-brand-orange' : ''}
|
||||
`}
|
||||
>
|
||||
{day}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between gap-4 pt-4 border-t border-neutral-200 dark:border-neutral-800">
|
||||
<div className="text-sm text-neutral-500">
|
||||
<span className={selectingStart ? 'text-brand-orange font-medium' : ''}>
|
||||
{startDate.toLocaleDateString()}
|
||||
</span>
|
||||
{' - '}
|
||||
<span className={!selectingStart ? 'text-brand-orange font-medium' : ''}>
|
||||
{endDate.toLocaleDateString()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 text-sm font-medium text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onApply({ start: formatDate(startDate), end: formatDate(endDate) })}
|
||||
className="px-4 py-2 text-sm font-medium bg-brand-orange text-white rounded-xl shadow-sm hover:bg-brand-orange-hover transition-colors"
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
|
||||
interface Option {
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
|
||||
interface SelectProps {
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
options: Option[]
|
||||
className?: string
|
||||
/** Form-field style (input-like). Default: toolbar style (btn-secondary). */
|
||||
variant?: 'default' | 'input'
|
||||
/** Shown when value is empty or does not match any option. */
|
||||
placeholder?: string
|
||||
/** Full-width trigger and panel. Use in form layouts. */
|
||||
fullWidth?: boolean
|
||||
/** Id for the trigger (e.g. for label htmlFor). */
|
||||
id?: string
|
||||
/** Alignment of the dropdown panel. */
|
||||
align?: 'left' | 'right'
|
||||
}
|
||||
|
||||
export default function Select({
|
||||
value,
|
||||
onChange,
|
||||
options,
|
||||
className = '',
|
||||
variant = 'default',
|
||||
placeholder,
|
||||
fullWidth = false,
|
||||
id,
|
||||
align = 'right',
|
||||
}: SelectProps) {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (ref.current && !ref.current.contains(event.target as Node)) {
|
||||
setIsOpen(false)
|
||||
}
|
||||
}
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||
}, [])
|
||||
|
||||
const selectedOption = options.find((o) => o.value === value)
|
||||
const displayLabel = selectedOption?.label ?? placeholder ?? value ?? ''
|
||||
|
||||
const triggerBase =
|
||||
variant === 'input'
|
||||
? 'px-4 py-2 border border-neutral-200 dark:border-neutral-800 rounded-xl bg-neutral-50/50 dark:bg-neutral-900/50 text-neutral-900 dark:text-white text-left text-sm ' +
|
||||
'focus:outline-none focus:border-brand-orange focus:ring-4 focus:ring-brand-orange/10 transition-all duration-200 ' +
|
||||
(isOpen ? 'ring-4 ring-brand-orange/10 border-brand-orange' : '')
|
||||
: 'btn-secondary min-w-[140px]'
|
||||
|
||||
const triggerLayout = fullWidth ? 'w-full ' : ''
|
||||
const alignClass = align === 'left' ? 'left-0' : 'right-0'
|
||||
const panelMinW = fullWidth ? 'w-full' : 'min-w-[140px] w-full'
|
||||
|
||||
return (
|
||||
<div className={`relative ${fullWidth ? 'w-full' : ''} ${className}`} ref={ref}>
|
||||
<button
|
||||
type="button"
|
||||
id={id}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className={`${triggerLayout}${triggerBase} flex items-center justify-between gap-2`}
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={isOpen}
|
||||
>
|
||||
<span className={!selectedOption && placeholder ? 'text-neutral-500 dark:text-neutral-400' : ''}>
|
||||
{displayLabel}
|
||||
</span>
|
||||
<svg
|
||||
className={`w-4 h-4 text-neutral-500 flex-shrink-0 transition-transform duration-200 ${isOpen ? 'rotate-180' : ''}`}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
<div
|
||||
className={`absolute ${alignClass} mt-2 ${panelMinW} bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-xl shadow-lg z-50 overflow-hidden py-1 max-h-60 overflow-y-auto`}
|
||||
role="listbox"
|
||||
>
|
||||
{options.map((option) => (
|
||||
<button
|
||||
key={option.value}
|
||||
role="option"
|
||||
aria-selected={value === option.value}
|
||||
onClick={() => {
|
||||
onChange(option.value)
|
||||
setIsOpen(false)
|
||||
}}
|
||||
className={`w-full text-left px-4 py-2.5 text-sm transition-colors duration-200 flex items-center justify-between
|
||||
${value === option.value
|
||||
? 'bg-neutral-50 dark:bg-neutral-800 text-brand-orange font-medium'
|
||||
: 'text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-800'
|
||||
}
|
||||
`}
|
||||
>
|
||||
{option.label}
|
||||
{value === option.value && (
|
||||
<svg className="w-4 h-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -8,7 +8,7 @@
|
||||
"name": "pulse-frontend",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@ciphera-net/ui": "^0.0.13",
|
||||
"@ciphera-net/ui": "^0.0.14",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"axios": "^1.13.2",
|
||||
"country-flag-icons": "^1.6.4",
|
||||
@@ -268,9 +268,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ciphera-net/ui": {
|
||||
"version": "0.0.13",
|
||||
"resolved": "https://npm.pkg.github.com/download/@ciphera-net/ui/0.0.13/0bcb7f064f6212cf701f647bc07596530e9db9c9",
|
||||
"integrity": "sha512-edp2igwPpZDdwHol+jkqdRU3oDFxijyBQtLwjjuoZc2/hnGmHJRsnpKzd4Eo0tY1PuWrHv/QP4nDgYPG2819CQ==",
|
||||
"version": "0.0.14",
|
||||
"resolved": "https://npm.pkg.github.com/download/@ciphera-net/ui/0.0.14/fb56b1bbd138eddc5a16d26c26d58524821f78c8",
|
||||
"integrity": "sha512-IcQnp8pr7qsCU1QLKCUad7i+H0l/MykwHiu7pvbEON31PeFEJj8pdkXYnp+0ihRunWQ73G5Jik44AZtqHgNyFg==",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"clsx": "^2.1.0",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ciphera-net/ui": "^0.0.13",
|
||||
"@ciphera-net/ui": "^0.0.14",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"axios": "^1.13.2",
|
||||
"country-flag-icons": "^1.6.4",
|
||||
|
||||
@@ -8,54 +8,13 @@
|
||||
--color-success: #10B981;
|
||||
--color-warning: #F59E0B;
|
||||
--color-error: #EF4444;
|
||||
|
||||
/* * Brand colors - Orange used as accent only */
|
||||
--color-brand-orange: #FD5E0F;
|
||||
|
||||
/* * Neutral greys for UI */
|
||||
--color-neutral-50: #fafafa;
|
||||
--color-neutral-100: #f5f5f5;
|
||||
--color-neutral-200: #e5e5e5;
|
||||
--color-neutral-300: #d4d4d4;
|
||||
--color-neutral-400: #a3a3a3;
|
||||
--color-neutral-500: #737373;
|
||||
--color-neutral-600: #525252;
|
||||
--color-neutral-700: #404040;
|
||||
--color-neutral-800: #262626;
|
||||
--color-neutral-900: #171717;
|
||||
|
||||
/* * Dark mode support */
|
||||
--color-bg: #ffffff;
|
||||
--color-text: #171717;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--color-bg: #0a0a0a;
|
||||
--color-text: #fafafa;
|
||||
}
|
||||
|
||||
* {
|
||||
@apply border-neutral-200 dark:border-neutral-800 transition-colors duration-300 ease-in-out;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-white dark:bg-neutral-950 text-neutral-900 dark:text-neutral-50 transition-colors duration-300 ease-in-out;
|
||||
@apply bg-ciphera-gradient bg-fixed;
|
||||
font-family: var(--font-plus-jakarta-sans), system-ui, sans-serif;
|
||||
}
|
||||
|
||||
.dark body {
|
||||
@apply bg-ciphera-gradient-dark;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
/* * Reusable component styles */
|
||||
.btn-primary {
|
||||
@apply bg-brand-orange text-white px-5 py-2.5 rounded-xl font-semibold shadow-sm shadow-orange-200 dark:shadow-none hover:shadow-orange-300 dark:hover:shadow-brand-orange/20 hover:-translate-y-0.5 transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-brand-orange focus:ring-offset-2 dark:focus:ring-offset-neutral-900;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@apply bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 text-neutral-900 dark:text-white px-5 py-2.5 rounded-xl font-medium hover:bg-neutral-50 dark:hover:bg-neutral-800 transition-all duration-200 shadow-sm hover:shadow-md dark:shadow-none focus:outline-none focus:ring-2 focus:ring-neutral-200 dark:focus:ring-neutral-700 focus:ring-offset-2 dark:focus:ring-offset-neutral-900;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user