fix(UI): switch to tabler icons only for consistency

This commit is contained in:
Jake Turner 2026-01-31 22:09:57 +00:00 committed by Jake Turner
parent 243f749090
commit 0da050c5a3
26 changed files with 104 additions and 143 deletions

View File

@ -1,5 +1,6 @@
import * as Icons from '@heroicons/react/24/solid' import * as Icons from '@tabler/icons-react'
import classNames from '~/lib/classNames' import classNames from '~/lib/classNames'
import DynamicIcon from './DynamicIcon'
export type AlertProps = React.HTMLAttributes<HTMLDivElement> & { export type AlertProps = React.HTMLAttributes<HTMLDivElement> & {
title: string title: string
@ -26,26 +27,18 @@ export default function Alert({
const getDefaultIcon = (): keyof typeof Icons => { const getDefaultIcon = (): keyof typeof Icons => {
switch (type) { switch (type) {
case 'warning': case 'warning':
return 'ExclamationTriangleIcon' return 'IconAlertTriangle'
case 'error': case 'error':
return 'XCircleIcon' return 'IconXboxX'
case 'success': case 'success':
return 'CheckCircleIcon' return 'IconCircleCheck'
case 'info': case 'info':
return 'InformationCircleIcon' return 'IconInfoCircle'
default: default:
return 'InformationCircleIcon' return 'IconInfoCircle'
} }
} }
const IconComponent = () => {
const iconName = icon || getDefaultIcon()
const Icon = Icons[iconName]
if (!Icon) return null
return <Icon aria-hidden="true" className={classNames('size-5 shrink-0', getIconColor())} />
}
const getIconColor = () => { const getIconColor = () => {
if (variant === 'solid') return 'text-desert-white' if (variant === 'solid') return 'text-desert-white'
switch (type) { switch (type) {
@ -165,7 +158,7 @@ export default function Alert({
return ( return (
<div {...props} className={classNames(getVariantStyles(), 'p-4', props.className)} role="alert"> <div {...props} className={classNames(getVariantStyles(), 'p-4', props.className)} role="alert">
<div className="flex gap-3"> <div className="flex gap-3">
<IconComponent /> <DynamicIcon icon={getDefaultIcon()} className={getIconColor() + ' size-5 shrink-0'} />
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h3 className={classNames('text-sm font-semibold', getTitleColor())}>{title}</h3> <h3 className={classNames('text-sm font-semibold', getTitleColor())}>{title}</h3>
@ -192,7 +185,7 @@ export default function Alert({
)} )}
aria-label="Dismiss alert" aria-label="Dismiss alert"
> >
<Icons.XMarkIcon className="size-5" /> <DynamicIcon icon="IconX" className="size-5" />
</button> </button>
)} )}
</div> </div>

View File

@ -1,5 +1,5 @@
import { IconRefresh } from '@tabler/icons-react'
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { ArrowPathIcon } from '@heroicons/react/24/outline'
import { import {
ADJECTIVES, ADJECTIVES,
NOUNS, NOUNS,
@ -118,7 +118,7 @@ export default function BuilderTagSelector({
className="p-2 text-desert-stone-dark hover:text-desert-green hover:bg-desert-stone-lighter rounded-lg transition-colors disabled:opacity-50" className="p-2 text-desert-stone-dark hover:text-desert-green hover:bg-desert-stone-lighter rounded-lg transition-colors disabled:opacity-50"
title="Randomize" title="Randomize"
> >
<ArrowPathIcon className="w-5 h-5" /> <IconRefresh className="w-5 h-5" />
</button> </button>
</div> </div>

View File

@ -55,7 +55,7 @@ const DownloadURLModal: React.FC<DownloadURLModalProps> = ({
onConfirm={() => runPreflightCheck(url)} onConfirm={() => runPreflightCheck(url)}
open={true} open={true}
confirmText="Download" confirmText="Download"
confirmIcon="ArrowDownTrayIcon" confirmIcon="IconDownload"
cancelText="Cancel" cancelText="Cancel"
confirmVariant="primary" confirmVariant="primary"
confirmLoading={loading} confirmLoading={loading}

View File

@ -1,4 +1,4 @@
import { InformationCircleIcon } from '@heroicons/react/24/outline' import { IconInfoCircle } from '@tabler/icons-react'
import { useState } from 'react' import { useState } from 'react'
interface InfoTooltipProps { interface InfoTooltipProps {
@ -20,7 +20,7 @@ export default function InfoTooltip({ text, className = '' }: InfoTooltipProps)
onBlur={() => setIsVisible(false)} onBlur={() => setIsVisible(false)}
aria-label="More information" aria-label="More information"
> >
<InformationCircleIcon className="w-4 h-4" /> <IconInfoCircle className="w-4 h-4" />
</button> </button>
{isVisible && ( {isVisible && (
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 z-50"> <div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 z-50">

View File

@ -1,4 +1,4 @@
import { CheckCircleIcon } from '@heroicons/react/24/outline' import { IconCircleCheck } from '@tabler/icons-react'
import classNames from '~/lib/classNames' import classNames from '~/lib/classNames'
export type InstallActivityFeedProps = { export type InstallActivityFeedProps = {
@ -41,7 +41,7 @@ const InstallActivityFeed: React.FC<InstallActivityFeedProps> = ({ activity, cla
<> <>
<div className="relative flex size-6 flex-none items-center justify-center bg-transparent"> <div className="relative flex size-6 flex-none items-center justify-center bg-transparent">
{activityItem.type === 'completed' ? ( {activityItem.type === 'completed' ? (
<CheckCircleIcon aria-hidden="true" className="size-6 text-indigo-600" /> <IconCircleCheck aria-hidden="true" className="size-6 text-indigo-600" />
) : ( ) : (
<div className="size-1.5 rounded-full bg-gray-100 ring-1 ring-gray-300" /> <div className="size-1.5 rounded-full bg-gray-100 ring-1 ring-gray-300" />
)} )}

View File

@ -1,11 +1,11 @@
import * as Icons from '@heroicons/react/24/outline'
import { useMemo } from 'react' import { useMemo } from 'react'
import clsx from 'clsx' import clsx from 'clsx'
import DynamicIcon, { DynamicIconName} from './DynamicIcon'
import { IconRefresh } from '@tabler/icons-react'
export interface StyledButtonProps extends React.HTMLAttributes<HTMLButtonElement> { export interface StyledButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
children: React.ReactNode children: React.ReactNode
// icon should be one of the HeroIcon names, e.g. ArrowTopRightOnSquareIcon icon?: DynamicIconName
icon?: keyof typeof Icons
disabled?: boolean disabled?: boolean
variant?: 'primary' | 'secondary' | 'danger' | 'action' | 'success' | 'ghost' | 'outline' variant?: 'primary' | 'secondary' | 'danger' | 'action' | 'success' | 'ghost' | 'outline'
size?: 'sm' | 'md' | 'lg' size?: 'sm' | 'md' | 'lg'
@ -26,12 +26,6 @@ const StyledButton: React.FC<StyledButtonProps> = ({
return props.disabled || loading return props.disabled || loading
}, [props.disabled, loading]) }, [props.disabled, loading])
const IconComponent = () => {
if (!icon) return null
const Icon = Icons[icon]
return Icon ? <Icon className={getIconSize()} /> : null
}
const getIconSize = () => { const getIconSize = () => {
switch (size) { switch (size) {
case 'sm': case 'sm':
@ -136,7 +130,7 @@ const StyledButton: React.FC<StyledButtonProps> = ({
const getLoadingSpinner = () => { const getLoadingSpinner = () => {
const spinnerSize = size === 'sm' ? 'h-3.5 w-3.5' : size === 'lg' ? 'h-5 w-5' : 'h-4 w-4' const spinnerSize = size === 'sm' ? 'h-3.5 w-3.5' : size === 'lg' ? 'h-5 w-5' : 'h-4 w-4'
return ( return (
<Icons.ArrowPathIcon <IconRefresh
className={clsx(spinnerSize, 'animate-spin')} className={clsx(spinnerSize, 'animate-spin')}
/> />
) )
@ -168,7 +162,7 @@ const StyledButton: React.FC<StyledButtonProps> = ({
getLoadingSpinner() getLoadingSpinner()
) : ( ) : (
<> <>
{icon && <IconComponent />} {icon && <DynamicIcon icon={icon} className={getIconSize()} />}
{children} {children}
</> </>
)} )}

View File

@ -1,10 +1,10 @@
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
import { Dialog, DialogBackdrop, DialogPanel, TransitionChild } from '@headlessui/react' import { Dialog, DialogBackdrop, DialogPanel, TransitionChild } from '@headlessui/react'
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'
import classNames from '~/lib/classNames' import classNames from '~/lib/classNames'
import { IconArrowLeft } from '@tabler/icons-react' import { IconArrowLeft } from '@tabler/icons-react'
import { usePage } from '@inertiajs/react' import { usePage } from '@inertiajs/react'
import { UsePageProps } from '../../types/system' import { UsePageProps } from '../../types/system'
import { IconMenu2, IconX } from '@tabler/icons-react'
type SidebarItem = { type SidebarItem = {
name: string name: string
@ -89,7 +89,7 @@ const StyledSidebar: React.FC<StyledSidebarProps> = ({ title, items }) => {
className="absolute left-4 top-4 z-50 xl:hidden" className="absolute left-4 top-4 z-50 xl:hidden"
onClick={() => setSidebarOpen(true)} onClick={() => setSidebarOpen(true)}
> >
<Bars3Icon aria-hidden="true" className="size-8" /> <IconMenu2 aria-hidden="true" className="size-8" />
</button> </button>
{/* Mobile sidebar */} {/* Mobile sidebar */}
<Dialog open={sidebarOpen} onClose={setSidebarOpen} className="relative z-50 xl:hidden"> <Dialog open={sidebarOpen} onClose={setSidebarOpen} className="relative z-50 xl:hidden">
@ -111,7 +111,7 @@ const StyledSidebar: React.FC<StyledSidebarProps> = ({ title, items }) => {
className="-m-2.5 p-2.5" className="-m-2.5 p-2.5"
> >
<span className="sr-only">Close sidebar</span> <span className="sr-only">Close sidebar</span>
<XMarkIcon aria-hidden="true" className="size-6 text-white" /> <IconX aria-hidden="true" className="size-6 text-white" />
</button> </button>
</div> </div>
</TransitionChild> </TransitionChild>

View File

@ -1,4 +1,4 @@
import { ChatBubbleLeftRightIcon } from '@heroicons/react/24/outline' import { IconMessages } from '@tabler/icons-react'
interface ChatButtonProps { interface ChatButtonProps {
onClick: () => void onClick: () => void
@ -11,7 +11,7 @@ export default function ChatButton({ onClick }: ChatButtonProps) {
className="fixed bottom-6 right-6 z-40 p-4 bg-desert-green text-white rounded-full shadow-lg hover:bg-desert-green/90 transition-all duration-200 hover:scale-110 focus:outline-none focus:ring-2 focus:ring-desert-green focus:ring-offset-2 cursor-pointer" className="fixed bottom-6 right-6 z-40 p-4 bg-desert-green text-white rounded-full shadow-lg hover:bg-desert-green/90 transition-all duration-200 hover:scale-110 focus:outline-none focus:ring-2 focus:ring-desert-green focus:ring-offset-2 cursor-pointer"
aria-label="Open chat" aria-label="Open chat"
> >
<ChatBubbleLeftRightIcon className="h-6 w-6" /> <IconMessages className="h-6 w-6" />
</button> </button>
) )
} }

View File

@ -1,5 +1,4 @@
import { PaperAirplaneIcon } from '@heroicons/react/24/outline' import { IconSend, IconWand } from '@tabler/icons-react'
import { IconWand } from '@tabler/icons-react'
import { useState, useRef, useEffect } from 'react' import { useState, useRef, useEffect } from 'react'
import classNames from '~/lib/classNames' import classNames from '~/lib/classNames'
import { ChatMessage } from '../../../types/chat' import { ChatMessage } from '../../../types/chat'
@ -139,7 +138,7 @@ export default function ChatInterface({
{isLoading ? ( {isLoading ? (
<div className="h-6 w-6 border-2 border-white border-t-transparent rounded-full animate-spin" /> <div className="h-6 w-6 border-2 border-white border-t-transparent rounded-full animate-spin" />
) : ( ) : (
<PaperAirplaneIcon className="h-6 w-6" /> <IconSend className="h-6 w-6" />
)} )}
</button> </button>
</form> </form>

View File

@ -1,8 +1,8 @@
import { ChatBubbleLeftIcon } from '@heroicons/react/24/outline'
import classNames from '~/lib/classNames' import classNames from '~/lib/classNames'
import StyledButton from '../StyledButton' import StyledButton from '../StyledButton'
import { router } from '@inertiajs/react' import { router } from '@inertiajs/react'
import { ChatSession } from '../../../types/chat' import { ChatSession } from '../../../types/chat'
import { IconMessage } from '@tabler/icons-react'
interface ChatSidebarProps { interface ChatSidebarProps {
sessions: ChatSession[] sessions: ChatSession[]
@ -24,7 +24,7 @@ export default function ChatSidebar({
return ( return (
<div className="w-64 bg-gray-50 border-r border-gray-200 flex flex-col h-full"> <div className="w-64 bg-gray-50 border-r border-gray-200 flex flex-col h-full">
<div className="p-4 border-b border-gray-200 h-[75px] flex items-center justify-center"> <div className="p-4 border-b border-gray-200 h-[75px] flex items-center justify-center">
<StyledButton onClick={onNewChat} icon="PlusIcon" variant="primary" fullWidth> <StyledButton onClick={onNewChat} icon="IconPlus" variant="primary" fullWidth>
New Chat New Chat
</StyledButton> </StyledButton>
</div> </div>
@ -46,7 +46,7 @@ export default function ChatSidebar({
)} )}
> >
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<ChatBubbleLeftIcon <IconMessage
className={classNames( className={classNames(
'h-5 w-5 mt-0.5 flex-shrink-0', 'h-5 w-5 mt-0.5 flex-shrink-0',
activeSessionId === session.id ? 'text-white' : 'text-gray-400' activeSessionId === session.id ? 'text-white' : 'text-gray-400'
@ -83,7 +83,7 @@ export default function ChatSidebar({
router.visit('/home') router.visit('/home')
} }
}} }}
icon={isInModal ? 'ArrowTopRightOnSquareIcon' : 'HomeIcon'} icon={isInModal ? 'IconExternalLink' : 'IconHome'}
variant="outline" variant="outline"
size="sm" size="sm"
fullWidth fullWidth
@ -94,7 +94,7 @@ export default function ChatSidebar({
onClick={() => { onClick={() => {
router.visit('/settings/models') router.visit('/settings/models')
}} }}
icon="CircleStackIcon" icon="IconDatabase"
variant="primary" variant="primary"
size="sm" size="sm"
fullWidth fullWidth
@ -105,7 +105,7 @@ export default function ChatSidebar({
onClick={() => { onClick={() => {
router.visit('/knowledge-base') router.visit('/knowledge-base')
}} }}
icon="AcademicCapIcon" icon="IconBrain"
variant="primary" variant="primary"
size="sm" size="sm"
fullWidth fullWidth
@ -114,7 +114,7 @@ export default function ChatSidebar({
</StyledButton> </StyledButton>
<StyledButton <StyledButton
onClick={onClearHistory} onClick={onClearHistory}
icon="TrashIcon" icon="IconTrash"
variant="danger" variant="danger"
size="sm" size="sm"
fullWidth fullWidth

View File

@ -1,4 +1,3 @@
import { XMarkIcon } from '@heroicons/react/24/outline'
import { useState, useCallback, useEffect } from 'react' import { useState, useCallback, useEffect } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import ChatSidebar from './ChatSidebar' import ChatSidebar from './ChatSidebar'
@ -9,6 +8,7 @@ import { formatBytes } from '~/lib/util'
import { useModals } from '~/context/ModalContext' import { useModals } from '~/context/ModalContext'
import { ChatMessage } from '../../../types/chat' import { ChatMessage } from '../../../types/chat'
import classNames from '~/lib/classNames' import classNames from '~/lib/classNames'
import { IconX } from '@tabler/icons-react'
interface ChatProps { interface ChatProps {
enabled: boolean enabled: boolean
@ -254,7 +254,7 @@ export default function Chat({ enabled, isInModal, onClose }: ChatProps) {
}} }}
className="rounded-lg hover:bg-gray-100 transition-colors" className="rounded-lg hover:bg-gray-100 transition-colors"
> >
<XMarkIcon className="h-6 w-6 text-gray-500" /> <IconX className="h-6 w-6 text-gray-500" />
</button> </button>
)} )}
</div> </div>

View File

@ -1,26 +1,24 @@
import {
ChartBarIcon,
Cog6ToothIcon,
CommandLineIcon,
FolderIcon,
MagnifyingGlassIcon,
} from '@heroicons/react/24/outline'
import { import {
IconArrowBigUpLines, IconArrowBigUpLines,
IconChartBar,
IconDashboard, IconDashboard,
IconDatabaseStar, IconDatabaseStar,
IconFolder,
IconGavel, IconGavel,
IconMapRoute, IconMapRoute,
IconSettings,
IconTerminal2,
IconZoom
} from '@tabler/icons-react' } from '@tabler/icons-react'
import StyledSidebar from '~/components/StyledSidebar' import StyledSidebar from '~/components/StyledSidebar'
import { getServiceLink } from '~/lib/navigation' import { getServiceLink } from '~/lib/navigation'
const navigation = [ const navigation = [
{ name: 'AI Model Manager', href: '/settings/models', icon: IconDatabaseStar, current: false }, { name: 'AI Model Manager', href: '/settings/models', icon: IconDatabaseStar, current: false },
{ name: 'Apps', href: '/settings/apps', icon: CommandLineIcon, current: false }, { name: 'Apps', href: '/settings/apps', icon: IconTerminal2, current: false },
{ name: 'Benchmark', href: '/settings/benchmark', icon: ChartBarIcon, current: false }, { name: 'Benchmark', href: '/settings/benchmark', icon: IconChartBar, current: false },
{ name: 'Content Explorer', href: '/settings/zim/remote-explorer', icon: MagnifyingGlassIcon, current: false }, { name: 'Content Explorer', href: '/settings/zim/remote-explorer', icon: IconZoom, current: false },
{ name: 'Content Manager', href: '/settings/zim', icon: FolderIcon, current: false }, { name: 'Content Manager', href: '/settings/zim', icon: IconFolder, current: false },
{ name: 'Maps Manager', href: '/settings/maps', icon: IconMapRoute, current: false }, { name: 'Maps Manager', href: '/settings/maps', icon: IconMapRoute, current: false },
{ {
name: 'Service Logs & Metrics', name: 'Service Logs & Metrics',
@ -35,7 +33,7 @@ const navigation = [
icon: IconArrowBigUpLines, icon: IconArrowBigUpLines,
current: false, current: false,
}, },
{ name: 'System', href: '/settings/system', icon: Cog6ToothIcon, current: false }, { name: 'System', href: '/settings/system', icon: IconSettings, current: false },
{ name: 'Legal Notices', href: '/settings/legal', icon: IconGavel, current: false }, { name: 'Legal Notices', href: '/settings/legal', icon: IconGavel, current: false },
] ]

View File

@ -41,7 +41,7 @@ export default function EasySetupWizardComplete() {
/> />
<div className="flex justify-center mt-8 pt-4 border-t border-desert-stone-light"> <div className="flex justify-center mt-8 pt-4 border-t border-desert-stone-light">
<div className="flex space-x-4"> <div className="flex space-x-4">
<StyledButton onClick={() => router.visit('/home')} icon="HomeIcon"> <StyledButton onClick={() => router.visit('/home')} icon="IconHome">
Go to Home Go to Home
</StyledButton> </StyledButton>
</div> </div>

View File

@ -1210,7 +1210,7 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim
onClick={handleBack} onClick={handleBack}
disabled={isProcessing} disabled={isProcessing}
variant="outline" variant="outline"
icon="ChevronLeftIcon" icon="IconChevronLeft"
> >
Back Back
</StyledButton> </StyledButton>
@ -1244,7 +1244,7 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim
onClick={handleNext} onClick={handleNext}
disabled={!canProceedToNextStep() || isProcessing} disabled={!canProceedToNextStep() || isProcessing}
variant="primary" variant="primary"
icon="ChevronRightIcon" icon="IconChevronRight"
> >
Next Next
</StyledButton> </StyledButton>
@ -1254,7 +1254,7 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim
disabled={isProcessing || !isOnline || !anySelectionMade} disabled={isProcessing || !isOnline || !anySelectionMade}
loading={isProcessing} loading={isProcessing}
variant="success" variant="success"
icon="CheckIcon" icon="IconCheck"
> >
Complete Setup Complete Setup
</StyledButton> </StyledButton>

View File

@ -64,7 +64,7 @@ export default function KnowledgeBase() {
<StyledButton <StyledButton
variant="primary" variant="primary"
size="lg" size="lg"
icon="ArrowUpCircleIcon" icon="IconUpload"
onClick={handleUpload} onClick={handleUpload}
disabled={files.length === 0 || uploadMutation.isPending} disabled={files.length === 0 || uploadMutation.isPending}
loading={uploadMutation.isPending} loading={uploadMutation.isPending}

View File

@ -24,7 +24,7 @@ export default function Maps(props: {
<p className="text-lg text-gray-600">Back to Home</p> <p className="text-lg text-gray-600">Back to Home</p>
</Link> </Link>
<Link href="/settings/maps"> <Link href="/settings/maps">
<StyledButton variant="primary" icon="Cog6ToothIcon"> <StyledButton variant="primary" icon="IconSettings">
Manage Map Regions Manage Map Regions
</StyledButton> </StyledButton>
</Link> </Link>
@ -39,7 +39,7 @@ export default function Maps(props: {
buttonProps={{ buttonProps={{
variant: 'secondary', variant: 'secondary',
children: 'Go to Map Settings', children: 'Go to Map Settings',
icon: 'Cog6ToothIcon', icon: 'IconSettings',
onClick: () => { onClick: () => {
window.location.href = '/settings/maps' window.location.href = '/settings/maps'
}, },

View File

@ -13,8 +13,7 @@ import LoadingSpinner from '~/components/LoadingSpinner'
import useErrorNotification from '~/hooks/useErrorNotification' import useErrorNotification from '~/hooks/useErrorNotification'
import useInternetStatus from '~/hooks/useInternetStatus' import useInternetStatus from '~/hooks/useInternetStatus'
import useServiceInstallationActivity from '~/hooks/useServiceInstallationActivity' import useServiceInstallationActivity from '~/hooks/useServiceInstallationActivity'
import { ArrowDownTrayIcon } from '@heroicons/react/24/outline' import { IconCheck, IconDownload } from '@tabler/icons-react'
import { IconCheck } from '@tabler/icons-react'
export default function SettingsPage(props: { system: { services: ServiceSlim[] } }) { export default function SettingsPage(props: { system: { services: ServiceSlim[] } }) {
const { openModal, closeAllModals } = useModals() const { openModal, closeAllModals } = useModals()
@ -48,7 +47,7 @@ export default function SettingsPage(props: { system: { services: ServiceSlim[]
confirmText="Install" confirmText="Install"
cancelText="Cancel" cancelText="Cancel"
confirmVariant="primary" confirmVariant="primary"
icon={<ArrowDownTrayIcon className="h-12 w-12 text-desert-green" />} icon={<IconDownload className="h-12 w-12 text-desert-green" />}
> >
<p className="text-gray-700"> <p className="text-gray-700">
Are you sure you want to install {service.friendly_name || service.service_name}? This Are you sure you want to install {service.friendly_name || service.service_name}? This
@ -132,7 +131,7 @@ export default function SettingsPage(props: { system: { services: ServiceSlim[]
const AppActions = ({ record }: { record: ServiceSlim }) => { const AppActions = ({ record }: { record: ServiceSlim }) => {
const ForceReinstallButton = () => ( const ForceReinstallButton = () => (
<StyledButton <StyledButton
icon="ExclamationTriangleIcon" icon="IconDownload"
variant="action" variant="action"
onClick={() => { onClick={() => {
openModal( openModal(
@ -165,7 +164,7 @@ export default function SettingsPage(props: { system: { services: ServiceSlim[]
return ( return (
<div className="flex space-x-2"> <div className="flex space-x-2">
<StyledButton <StyledButton
icon={'ArrowDownTrayIcon'} icon={'IconDownload'}
variant="primary" variant="primary"
onClick={() => handleInstallService(record)} onClick={() => handleInstallService(record)}
disabled={isInstalling || !isOnline} disabled={isInstalling || !isOnline}
@ -181,7 +180,7 @@ export default function SettingsPage(props: { system: { services: ServiceSlim[]
return ( return (
<div className="flex space-x-2"> <div className="flex space-x-2">
<StyledButton <StyledButton
icon={'ArrowTopRightOnSquareIcon'} icon={'IconExternalLink'}
onClick={() => { onClick={() => {
window.open(getServiceLink(record.ui_location || 'unknown'), '_blank') window.open(getServiceLink(record.ui_location || 'unknown'), '_blank')
}} }}
@ -191,7 +190,7 @@ export default function SettingsPage(props: { system: { services: ServiceSlim[]
{record.status && record.status !== 'unknown' && ( {record.status && record.status !== 'unknown' && (
<> <>
<StyledButton <StyledButton
icon={record.status === 'running' ? 'StopIcon' : 'PlayIcon'} icon={record.status === 'running' ? 'IconPlayerStop' : 'IconPlayerPlay'}
variant={record.status === 'running' ? 'action' : undefined} variant={record.status === 'running' ? 'action' : undefined}
onClick={() => { onClick={() => {
openModal( openModal(
@ -219,7 +218,7 @@ export default function SettingsPage(props: { system: { services: ServiceSlim[]
</StyledButton> </StyledButton>
{record.status === 'running' && ( {record.status === 'running' && (
<StyledButton <StyledButton
icon="ArrowPathIcon" icon="IconRefresh"
variant="action" variant="action"
onClick={() => { onClick={() => {
openModal( openModal(

View File

@ -8,15 +8,7 @@ import Alert from '~/components/Alert'
import StyledButton from '~/components/StyledButton' import StyledButton from '~/components/StyledButton'
import InfoTooltip from '~/components/InfoTooltip' import InfoTooltip from '~/components/InfoTooltip'
import BuilderTagSelector from '~/components/BuilderTagSelector' import BuilderTagSelector from '~/components/BuilderTagSelector'
import { import { IconRobot, IconChartBar, IconCpu, IconDatabase, IconServer, IconChevronDown, IconClock } from '@tabler/icons-react'
ChartBarIcon,
CpuChipIcon,
CircleStackIcon,
ServerIcon,
ChevronDownIcon,
ClockIcon,
} from '@heroicons/react/24/outline'
import { IconRobot } from '@tabler/icons-react'
import { useTransmit } from 'react-adonis-transmit' import { useTransmit } from 'react-adonis-transmit'
import { BenchmarkProgress, BenchmarkStatus } from '../../../types/benchmark' import { BenchmarkProgress, BenchmarkStatus } from '../../../types/benchmark'
import BenchmarkResult from '#models/benchmark_result' import BenchmarkResult from '#models/benchmark_result'
@ -378,7 +370,7 @@ export default function BenchmarkPage(props: {
<StyledButton <StyledButton
onClick={handleFullBenchmarkClick} onClick={handleFullBenchmarkClick}
disabled={runBenchmark.isPending} disabled={runBenchmark.isPending}
icon='PlayIcon' icon='IconPlayerPlay'
> >
Run Full Benchmark Run Full Benchmark
</StyledButton> </StyledButton>
@ -386,7 +378,7 @@ export default function BenchmarkPage(props: {
variant="secondary" variant="secondary"
onClick={() => runBenchmark.mutate('system')} onClick={() => runBenchmark.mutate('system')}
disabled={runBenchmark.isPending} disabled={runBenchmark.isPending}
icon='CpuChipIcon' icon='IconCpu'
> >
System Only System Only
</StyledButton> </StyledButton>
@ -394,7 +386,7 @@ export default function BenchmarkPage(props: {
variant="secondary" variant="secondary"
onClick={() => runBenchmark.mutate('ai')} onClick={() => runBenchmark.mutate('ai')}
disabled={runBenchmark.isPending || !aiInstalled} disabled={runBenchmark.isPending || !aiInstalled}
icon='SparklesIcon' icon='IconWand'
title={!aiInstalled ? 'AI Assistant must be installed to run AI benchmark' : undefined} title={!aiInstalled ? 'AI Assistant must be installed to run AI benchmark' : undefined}
> >
AI Only AI Only
@ -431,7 +423,7 @@ export default function BenchmarkPage(props: {
size="lg" size="lg"
variant="cpu" variant="cpu"
subtext="out of 100" subtext="out of 100"
icon={<ChartBarIcon className="w-8 h-8" />} icon={<IconChartBar className="w-8 h-8" />}
/> />
</div> </div>
<div className="flex-1 space-y-4"> <div className="flex-1 space-y-4">
@ -482,7 +474,7 @@ export default function BenchmarkPage(props: {
anonymous: shareAnonymously anonymous: shareAnonymously
})} })}
disabled={submitResult.isPending} disabled={submitResult.isPending}
icon='CloudArrowUpIcon' icon='IconCloudUpload'
> >
{submitResult.isPending ? 'Submitting...' : 'Share with Community'} {submitResult.isPending ? 'Submitting...' : 'Share with Community'}
</StyledButton> </StyledButton>
@ -544,7 +536,7 @@ export default function BenchmarkPage(props: {
label="CPU" label="CPU"
size="md" size="md"
variant="cpu" variant="cpu"
icon={<CpuChipIcon className="w-6 h-6" />} icon={<IconCpu className="w-6 h-6" />}
/> />
</div> </div>
<div className="bg-desert-white rounded-lg p-6 border border-desert-stone-light shadow-sm"> <div className="bg-desert-white rounded-lg p-6 border border-desert-stone-light shadow-sm">
@ -553,7 +545,7 @@ export default function BenchmarkPage(props: {
label="Memory" label="Memory"
size="md" size="md"
variant="memory" variant="memory"
icon={<CircleStackIcon className="w-6 h-6" />} icon={<IconDatabase className="w-6 h-6" />}
/> />
</div> </div>
<div className="bg-desert-white rounded-lg p-6 border border-desert-stone-light shadow-sm"> <div className="bg-desert-white rounded-lg p-6 border border-desert-stone-light shadow-sm">
@ -562,7 +554,7 @@ export default function BenchmarkPage(props: {
label="Disk Read" label="Disk Read"
size="md" size="md"
variant="disk" variant="disk"
icon={<ServerIcon className="w-6 h-6" />} icon={<IconServer className="w-6 h-6" />}
/> />
</div> </div>
<div className="bg-desert-white rounded-lg p-6 border border-desert-stone-light shadow-sm"> <div className="bg-desert-white rounded-lg p-6 border border-desert-stone-light shadow-sm">
@ -571,7 +563,7 @@ export default function BenchmarkPage(props: {
label="Disk Write" label="Disk Write"
size="md" size="md"
variant="disk" variant="disk"
icon={<ServerIcon className="w-6 h-6" />} icon={<IconServer className="w-6 h-6" />}
/> />
</div> </div>
</div> </div>
@ -646,7 +638,7 @@ export default function BenchmarkPage(props: {
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<InfoCard <InfoCard
title="Processor" title="Processor"
icon={<CpuChipIcon className="w-6 h-6" />} icon={<IconCpu className="w-6 h-6" />}
variant="elevated" variant="elevated"
data={[ data={[
{ label: 'Model', value: latestResult.cpu_model }, { label: 'Model', value: latestResult.cpu_model },
@ -656,7 +648,7 @@ export default function BenchmarkPage(props: {
/> />
<InfoCard <InfoCard
title="System" title="System"
icon={<ServerIcon className="w-6 h-6" />} icon={<IconServer className="w-6 h-6" />}
variant="elevated" variant="elevated"
data={[ data={[
{ label: 'RAM', value: formatBytes(latestResult.ram_bytes) }, { label: 'RAM', value: formatBytes(latestResult.ram_bytes) },
@ -697,7 +689,7 @@ export default function BenchmarkPage(props: {
<div className="font-bold text-desert-green">{latestResult.nomad_score.toFixed(1)}</div> <div className="font-bold text-desert-green">{latestResult.nomad_score.toFixed(1)}</div>
</div> </div>
</div> </div>
<ChevronDownIcon <IconChevronDown
className={`w-5 h-5 text-desert-stone-dark transition-transform ${showDetails ? 'rotate-180' : ''}`} className={`w-5 h-5 text-desert-stone-dark transition-transform ${showDetails ? 'rotate-180' : ''}`}
/> />
</button> </button>
@ -799,12 +791,12 @@ export default function BenchmarkPage(props: {
className="w-full p-4 flex items-center justify-between hover:bg-desert-stone-lighter/30 transition-colors" className="w-full p-4 flex items-center justify-between hover:bg-desert-stone-lighter/30 transition-colors"
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<ClockIcon className="w-5 h-5 text-desert-stone-dark" /> <IconClock className="w-5 h-5 text-desert-stone-dark" />
<span className="font-medium text-desert-green"> <span className="font-medium text-desert-green">
{benchmarkHistory.length} benchmark{benchmarkHistory.length !== 1 ? 's' : ''} recorded {benchmarkHistory.length} benchmark{benchmarkHistory.length !== 1 ? 's' : ''} recorded
</span> </span>
</div> </div>
<ChevronDownIcon <IconChevronDown
className={`w-5 h-5 text-desert-stone-dark transition-transform ${showHistory ? 'rotate-180' : ''}`} className={`w-5 h-5 text-desert-stone-dark transition-transform ${showHistory ? 'rotate-180' : ''}`}
/> />
</button> </button>

View File

@ -198,7 +198,7 @@ export default function MapsManager(props: {
variant="primary" variant="primary"
onClick={openDownloadModal} onClick={openDownloadModal}
loading={downloading} loading={downloading}
icon="CloudArrowDownIcon" icon="IconCloudDownload"
> >
Download Custom Map File Download Custom Map File
</StyledButton> </StyledButton>
@ -213,7 +213,7 @@ export default function MapsManager(props: {
buttonProps={{ buttonProps={{
variant: 'secondary', variant: 'secondary',
children: 'Download Base Assets', children: 'Download Base Assets',
icon: 'ArrowDownTrayIcon', icon: 'IconDownload',
loading: downloading, loading: downloading,
onClick: () => downloadBaseAssets(), onClick: () => downloadBaseAssets(),
}} }}
@ -223,7 +223,7 @@ export default function MapsManager(props: {
<StyledButton <StyledButton
onClick={() => fetchLatestCollections.mutate()} onClick={() => fetchLatestCollections.mutate()}
disabled={fetchLatestCollections.isPending} disabled={fetchLatestCollections.isPending}
icon="CloudArrowDownIcon" icon="IconCloudDownload"
> >
Fetch Latest Collections Fetch Latest Collections
</StyledButton> </StyledButton>
@ -254,7 +254,7 @@ export default function MapsManager(props: {
<div className="flex space-x-2"> <div className="flex space-x-2">
<StyledButton <StyledButton
variant="danger" variant="danger"
icon={'TrashIcon'} icon={'IconTrash'}
onClick={() => { onClick={() => {
confirmDeleteFile(record) confirmDeleteFile(record)
}} }}

View File

@ -5,15 +5,10 @@ import { formatBytes } from '~/lib/util'
import CircularGauge from '~/components/systeminfo/CircularGauge' import CircularGauge from '~/components/systeminfo/CircularGauge'
import HorizontalBarChart from '~/components/HorizontalBarChart' import HorizontalBarChart from '~/components/HorizontalBarChart'
import InfoCard from '~/components/systeminfo/InfoCard' import InfoCard from '~/components/systeminfo/InfoCard'
import {
CpuChipIcon,
CircleStackIcon,
ServerIcon,
ComputerDesktopIcon,
} from '@heroicons/react/24/outline'
import Alert from '~/components/Alert' import Alert from '~/components/Alert'
import { useSystemInfo } from '~/hooks/useSystemInfo' import { useSystemInfo } from '~/hooks/useSystemInfo'
import StatusCard from '~/components/systeminfo/StatusCard' import StatusCard from '~/components/systeminfo/StatusCard'
import { IconCpu, IconDatabase, IconServer, IconDeviceDesktop } from '@tabler/icons-react'
export default function SettingsPage(props: { export default function SettingsPage(props: {
system: { info: SystemInformationResponse | undefined } system: { info: SystemInformationResponse | undefined }
@ -35,7 +30,13 @@ export default function SettingsPage(props: {
// Build storage display items - fall back to fsSize when disk array is empty // Build storage display items - fall back to fsSize when disk array is empty
// (Same approach as Easy Setup wizard fix from PR #90) // (Same approach as Easy Setup wizard fix from PR #90)
const validDisks = info?.disk?.filter((d) => d.totalSize > 0) || [] const validDisks = info?.disk?.filter((d) => d.totalSize > 0) || []
let storageItems: { label: string; value: number; total: string; used: string; subtext: string }[] = [] let storageItems: {
label: string
value: number
total: string
used: string
subtext: string
}[] = []
if (validDisks.length > 0) { if (validDisks.length > 0) {
storageItems = validDisks.map((disk) => ({ storageItems = validDisks.map((disk) => ({
label: disk.name || 'Unknown', label: disk.name || 'Unknown',
@ -100,7 +101,7 @@ export default function SettingsPage(props: {
size="lg" size="lg"
variant="cpu" variant="cpu"
subtext={`${info?.cpu.cores || 0} cores`} subtext={`${info?.cpu.cores || 0} cores`}
icon={<CpuChipIcon className="w-8 h-8" />} icon={<IconCpu className="w-8 h-8" />}
/> />
</div> </div>
<div className="bg-desert-white rounded-lg p-6 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow"> <div className="bg-desert-white rounded-lg p-6 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow">
@ -110,7 +111,7 @@ export default function SettingsPage(props: {
size="lg" size="lg"
variant="memory" variant="memory"
subtext={`${formatBytes(info?.mem.used || 0)} / ${formatBytes(info?.mem.total || 0)}`} subtext={`${formatBytes(info?.mem.used || 0)} / ${formatBytes(info?.mem.total || 0)}`}
icon={<CircleStackIcon className="w-8 h-8" />} icon={<IconDatabase className="w-8 h-8" />}
/> />
</div> </div>
<div className="bg-desert-white rounded-lg p-6 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow"> <div className="bg-desert-white rounded-lg p-6 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow">
@ -120,7 +121,7 @@ export default function SettingsPage(props: {
size="lg" size="lg"
variant="disk" variant="disk"
subtext={`${formatBytes(info?.mem.swapused || 0)} / ${formatBytes(info?.mem.swaptotal || 0)}`} subtext={`${formatBytes(info?.mem.swapused || 0)} / ${formatBytes(info?.mem.swaptotal || 0)}`}
icon={<ServerIcon className="w-8 h-8" />} icon={<IconServer className="w-8 h-8" />}
/> />
</div> </div>
</div> </div>
@ -134,7 +135,7 @@ export default function SettingsPage(props: {
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<InfoCard <InfoCard
title="Operating System" title="Operating System"
icon={<ComputerDesktopIcon className="w-6 h-6" />} icon={<IconDeviceDesktop className="w-6 h-6" />}
variant="elevated" variant="elevated"
data={[ data={[
{ label: 'Distribution', value: info?.os.distro }, { label: 'Distribution', value: info?.os.distro },
@ -146,7 +147,7 @@ export default function SettingsPage(props: {
/> />
<InfoCard <InfoCard
title="Processor" title="Processor"
icon={<CpuChipIcon className="w-6 h-6" />} icon={<IconCpu className="w-6 h-6" />}
variant="elevated" variant="elevated"
data={[ data={[
{ label: 'Manufacturer', value: info?.cpu.manufacturer }, { label: 'Manufacturer', value: info?.cpu.manufacturer },

View File

@ -261,7 +261,7 @@ export default function SystemUpdatePage(props: {
<StyledButton <StyledButton
variant="primary" variant="primary"
size="lg" size="lg"
icon="ArrowDownTrayIcon" icon="IconDownload"
onClick={handleStartUpdate} onClick={handleStartUpdate}
disabled={!props.system.updateAvailable} disabled={!props.system.updateAvailable}
> >
@ -270,7 +270,7 @@ export default function SystemUpdatePage(props: {
<StyledButton <StyledButton
variant="ghost" variant="ghost"
size="lg" size="lg"
icon="ArrowPathIcon" icon="IconRefresh"
onClick={() => window.location.reload()} onClick={() => window.location.reload()}
> >
Check Again Check Again
@ -323,7 +323,7 @@ export default function SystemUpdatePage(props: {
<StyledButton <StyledButton
variant="ghost" variant="ghost"
size="sm" size="sm"
icon="DocumentTextIcon" icon="IconLogs"
onClick={handleViewLogs} onClick={handleViewLogs}
fullWidth fullWidth
> >

View File

@ -88,7 +88,7 @@ export default function ZimPage() {
<div className="flex space-x-2"> <div className="flex space-x-2">
<StyledButton <StyledButton
variant="danger" variant="danger"
icon={'TrashIcon'} icon={'IconTrash'}
onClick={() => { onClick={() => {
confirmDeleteFile(record) confirmDeleteFile(record)
}} }}

View File

@ -303,7 +303,7 @@ export default function ZimRemoteExplorer() {
<StyledButton <StyledButton
onClick={() => fetchLatestCollections.mutate()} onClick={() => fetchLatestCollections.mutate()}
disabled={fetchLatestCollections.isPending} disabled={fetchLatestCollections.isPending}
icon="CloudArrowDownIcon" icon="IconCloudDownload"
> >
Fetch Latest Collections Fetch Latest Collections
</StyledButton> </StyledButton>
@ -404,7 +404,7 @@ export default function ZimRemoteExplorer() {
return ( return (
<div className="flex space-x-2"> <div className="flex space-x-2">
<StyledButton <StyledButton
icon={'ArrowDownTrayIcon'} icon={'IconDownload'}
onClick={() => { onClick={() => {
confirmDownload(record) confirmDownload(record)
}} }}

View File

@ -1,10 +1,6 @@
import { useState } from 'react' import { useState } from 'react'
import { NotificationContext, Notification } from '../context/NotificationContext' import { NotificationContext, Notification } from '../context/NotificationContext'
import { import { IconExclamationCircle, IconCircleCheck, IconInfoCircle } from '@tabler/icons-react'
CheckCircleIcon,
InformationCircleIcon,
ExclamationTriangleIcon,
} from '@heroicons/react/24/outline'
const NotificationsProvider = ({ children }: { children: React.ReactNode }) => { const NotificationsProvider = ({ children }: { children: React.ReactNode }) => {
const [notifications, setNotifications] = useState<(Notification & { id: string })[]>([]) const [notifications, setNotifications] = useState<(Notification & { id: string })[]>([])
@ -32,13 +28,13 @@ const NotificationsProvider = ({ children }: { children: React.ReactNode }) => {
const Icon = ({ type }: { type: string }) => { const Icon = ({ type }: { type: string }) => {
switch (type) { switch (type) {
case 'error': case 'error':
return <ExclamationTriangleIcon className="h-5 w-5 text-red-500" /> return <IconExclamationCircle className="h-5 w-5 text-red-500" />
case 'success': case 'success':
return <CheckCircleIcon className="h-5 w-5 text-green-500" /> return <IconCircleCheck className="h-5 w-5 text-green-500" />
case 'info': case 'info':
return <InformationCircleIcon className="h-5 w-5 text-blue-500" /> return <IconInfoCircle className="h-5 w-5 text-blue-500" />
default: default:
return <InformationCircleIcon className="h-5 w-5 text-blue-500" /> return <IconInfoCircle className="h-5 w-5 text-blue-500" />
} }
} }

View File

@ -21,7 +21,6 @@
"@adonisjs/transmit-client": "^1.0.0", "@adonisjs/transmit-client": "^1.0.0",
"@adonisjs/vite": "^4.0.0", "@adonisjs/vite": "^4.0.0",
"@headlessui/react": "^2.2.4", "@headlessui/react": "^2.2.4",
"@heroicons/react": "^2.2.0",
"@inertiajs/react": "^2.0.13", "@inertiajs/react": "^2.0.13",
"@markdoc/markdoc": "^0.5.2", "@markdoc/markdoc": "^0.5.2",
"@protomaps/basemaps": "^5.7.0", "@protomaps/basemaps": "^5.7.0",
@ -2021,15 +2020,6 @@
"react-dom": "^18 || ^19 || ^19.0.0-rc" "react-dom": "^18 || ^19 || ^19.0.0-rc"
} }
}, },
"node_modules/@heroicons/react": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz",
"integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==",
"license": "MIT",
"peerDependencies": {
"react": ">= 16 || ^19.0.0-rc"
}
},
"node_modules/@humanfs/core": { "node_modules/@humanfs/core": {
"version": "0.19.1", "version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",

View File

@ -72,7 +72,6 @@
"@adonisjs/transmit-client": "^1.0.0", "@adonisjs/transmit-client": "^1.0.0",
"@adonisjs/vite": "^4.0.0", "@adonisjs/vite": "^4.0.0",
"@headlessui/react": "^2.2.4", "@headlessui/react": "^2.2.4",
"@heroicons/react": "^2.2.0",
"@inertiajs/react": "^2.0.13", "@inertiajs/react": "^2.0.13",
"@markdoc/markdoc": "^0.5.2", "@markdoc/markdoc": "^0.5.2",
"@protomaps/basemaps": "^5.7.0", "@protomaps/basemaps": "^5.7.0",