-
Library
+
+
Library
+
+
{filters.map(filter => (
- {
className={currentFilter === filter.id ? 'active' : ''}
onClick={() => setFilter(filter.id)}
>
- {filter.label}
+
+
+ {filter.label}
+
))}
diff --git a/src/components/notifications/Toast.tsx b/src/components/notifications/Toast.tsx
index 58d8eb4..cf2fc46 100644
--- a/src/components/notifications/Toast.tsx
+++ b/src/components/notifications/Toast.tsx
@@ -1,4 +1,5 @@
-import { ReactNode, useState, useEffect } from 'react'
+import { ReactNode, useState, useEffect, useCallback } from 'react'
+import { Icon, check, info, warning, error } from '@/components/icons'
export interface ToastProps {
id: string;
@@ -23,6 +24,13 @@ const Toast = ({
}: ToastProps) => {
const [visible, setVisible] = useState(false)
+ // Use useCallback to memoize the handleDismiss function
+ const handleDismiss = useCallback(() => {
+ setVisible(false)
+ // Give time for exit animation
+ setTimeout(() => onDismiss(id), 300)
+ }, [id, onDismiss])
+
// Handle animation on mount/unmount
useEffect(() => {
// Start the enter animation
@@ -42,30 +50,24 @@ const Toast = ({
clearTimeout(enterTimer)
if (dismissTimer) clearTimeout(dismissTimer)
}
- }, [duration])
+ }, [duration, handleDismiss])
// Get icon based on toast type
const getIcon = (): ReactNode => {
switch (type) {
case 'success':
- return '✓'
+ return
case 'error':
- return '✗'
+ return
case 'warning':
- return '⚠'
+ return
case 'info':
- return 'ℹ'
+ return
default:
- return ''
+ return
}
}
- const handleDismiss = () => {
- setVisible(false)
- // Give time for exit animation
- setTimeout(() => onDismiss(id), 300)
- }
-
return (
{getIcon()}
diff --git a/src/styles/components/buttons/_animated_checkbox.scss b/src/styles/components/buttons/_animated_checkbox.scss
index 54786e1..be4812e 100644
--- a/src/styles/components/buttons/_animated_checkbox.scss
+++ b/src/styles/components/buttons/_animated_checkbox.scss
@@ -42,23 +42,18 @@
border-color: var(--primary-color, #ffc896);
box-shadow: 0 0 10px rgba(255, 200, 150, 0.2);
}
-}
-.checkmark-icon {
- width: 18px;
- height: 18px;
-}
+ .checkbox-icon {
+ color: var(--text-heavy);
+ opacity: 0;
+ transform: scale(0);
+ transition: all 0.3s var(--easing-bounce);
+ }
-.checkmark {
- stroke-dasharray: 30;
- stroke-dashoffset: 30;
- opacity: 0;
- transition: stroke-dashoffset 0.3s ease;
-
- &.checked {
- stroke-dashoffset: 0;
+ &.checked .checkbox-icon {
opacity: 1;
- animation: checkmarkAnimation 0.3s cubic-bezier(0.65, 0, 0.45, 1) forwards;
+ transform: scale(1);
+ animation: checkbox-pop 0.3s var(--easing-bounce) forwards;
}
}
@@ -83,17 +78,18 @@
color: var(--text-muted);
}
-// Animation for the checkmark
-@keyframes checkmarkAnimation {
+// Animation for the checkbox
+@keyframes checkbox-pop {
0% {
- stroke-dashoffset: 30;
+ transform: scale(0);
opacity: 0;
}
- 40% {
+ 70% {
+ transform: scale(1.2);
opacity: 1;
}
100% {
- stroke-dashoffset: 0;
+ transform: scale(1);
opacity: 1;
}
}
diff --git a/src/styles/components/icons/_icon.scss b/src/styles/components/icons/_icon.scss
new file mode 100644
index 0000000..7e88e7e
--- /dev/null
+++ b/src/styles/components/icons/_icon.scss
@@ -0,0 +1,128 @@
+@use '../../themes/index' as *;
+@use '../../abstracts/index' as *;
+
+/*
+ Icon component styles
+*/
+.icon {
+ display: inline-flex;
+ vertical-align: middle;
+
+ // Base icon styling
+ &.icon-xs {
+ width: 12px;
+ height: 12px;
+ }
+
+ &.icon-sm {
+ width: 16px;
+ height: 16px;
+ }
+
+ &.icon-md {
+ width: 24px;
+ height: 24px;
+ }
+
+ &.icon-lg {
+ width: 32px;
+ height: 32px;
+ }
+
+ &.icon-xl {
+ width: 48px;
+ height: 48px;
+ }
+
+ // Interactive icons
+ &.icon-clickable {
+ cursor: pointer;
+ transition: transform 0.2s ease, opacity 0.2s ease;
+
+ &:hover {
+ opacity: 0.8;
+ transform: scale(1.1);
+ }
+
+ &:active {
+ transform: scale(0.95);
+ }
+ }
+
+ // Icon with background
+ &.icon-with-bg {
+ padding: 6px;
+ border-radius: 50%;
+ background-color: var(--border-soft);
+
+ &:hover {
+ background-color: var(--border);
+ }
+ }
+
+ // Color variations
+ &.icon-primary {
+ color: var(--primary-color);
+ }
+
+ &.icon-secondary {
+ color: var(--text-secondary);
+ }
+
+ &.icon-danger {
+ color: var(--danger);
+ }
+
+ &.icon-success {
+ color: var(--success);
+ }
+
+ &.icon-warning {
+ color: var(--warning);
+ }
+
+ &.icon-info {
+ color: var(--info);
+ }
+}
+
+// Icon in button
+.btn .icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+// Animated icons
+.icon-spin {
+ animation: icon-spin 2s linear infinite;
+}
+
+.icon-pulse {
+ animation: icon-pulse 1.5s ease-in-out infinite;
+}
+
+// Animations
+@keyframes icon-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes icon-pulse {
+ 0% {
+ opacity: 1;
+ transform: scale(1);
+ }
+ 50% {
+ opacity: 0.5;
+ transform: scale(0.95);
+ }
+ 100% {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
diff --git a/src/styles/components/icons/_index.scss b/src/styles/components/icons/_index.scss
new file mode 100644
index 0000000..72d5ead
--- /dev/null
+++ b/src/styles/components/icons/_index.scss
@@ -0,0 +1 @@
+@forward './icon';
diff --git a/src/styles/components/layout/_header.scss b/src/styles/components/layout/_header.scss
index 7b096d0..9ca0663 100644
--- a/src/styles/components/layout/_header.scss
+++ b/src/styles/components/layout/_header.scss
@@ -16,12 +16,23 @@
z-index: var(--z-header);
height: var(--header-height);
- h1 {
- font-size: 1.5rem;
- font-weight: 600;
- color: var(--text-primary);
- letter-spacing: 0.5px;
- text-shadow: 0 2px 4px rgba(0, 0, 0, 0.7);
+ .app-title {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+
+ h1 {
+ font-size: 1.5rem;
+ font-weight: 600;
+ color: var(--text-primary);
+ letter-spacing: 0.5px;
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.7);
+ }
+
+ .app-logo-icon {
+ color: var(--primary-color);
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
+ }
}
&::after {
@@ -57,12 +68,25 @@
align-items: center;
}
+.search-container {
+ position: relative;
+ display: flex;
+ align-items: center;
+
+ .search-icon {
+ position: absolute;
+ left: 0.8rem;
+ color: rgba(255, 255, 255, 0.4);
+ pointer-events: none;
+ }
+}
+
.search-input {
background-color: var(--border-dark);
border: 1px solid var(--border-soft);
border-radius: 4px;
color: var(--text-primary);
- padding: 0.6rem 1rem;
+ padding: 0.6rem 1rem 0.6rem 2.5rem;
font-size: 0.9rem;
transition: all var(--duration-normal) var(--easing-ease-out);
box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.2);
@@ -73,6 +97,10 @@
background-color: rgba(255, 255, 255, 0.1);
outline: none;
box-shadow: 0 0 0 2px rgba(var(--primary-color), 0.3), inset 0 2px 5px rgba(0, 0, 0, 0.2);
+
+ & + .search-icon {
+ color: var(--primary-color);
+ }
}
&::placeholder {
diff --git a/src/styles/components/layout/_sidebar.scss b/src/styles/components/layout/_sidebar.scss
index 64e9d75..75a6038 100644
--- a/src/styles/components/layout/_sidebar.scss
+++ b/src/styles/components/layout/_sidebar.scss
@@ -17,12 +17,15 @@
overflow-y: auto;
z-index: var(--z-elevate) + 1;
@include custom-scrollbar;
+}
+
+.sidebar-header {
+ margin-bottom: 1.5rem;
h2 {
color: var(--text-primary);
font-size: 1.1rem;
font-weight: 600;
- margin-bottom: 1rem;
letter-spacing: 0.5px;
opacity: 0.9;
}
@@ -52,74 +55,21 @@
);
box-shadow: 0 4px 10px rgba(var(--primary-color), 0.3);
color: var(--elevated-bg);
+
+ .filter-icon {
+ color: var(--elevated-bg);
+ }
}
}
}
-// Custom select dropdown styling
-.custom-select {
- position: relative;
- display: inline-block;
+.filter-item {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
- .select-selected {
- background-color: rgba(255, 255, 255, 0.07);
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: var(--radius-sm);
- color: var(--text-primary);
- padding: 0.6rem 1rem;
- font-size: 0.9rem;
- cursor: pointer;
- transition: all var(--duration-normal) var(--easing-ease-out);
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 10px;
- min-width: 150px;
-
- &:after {
- content: '⯆';
- font-size: 0.7rem;
- opacity: 0.7;
- }
-
- &:hover {
- background-color: rgba(255, 255, 255, 0.1);
- }
- }
-
- .select-items {
- position: absolute;
- top: 100%;
- left: 0;
- right: 0;
- background-color: var(--secondary-bg);
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: var(--radius-sm);
- margin-top: 5px;
- box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
- z-index: 10;
- max-height: 0;
- overflow: hidden;
- transition: max-height 0.3s ease;
-
- &.show {
- max-height: 300px;
- }
-
- .select-item {
- padding: 0.5rem 1rem;
- cursor: pointer;
- transition: all var(--duration-normal) var(--easing-ease-out);
-
- &:hover {
- background-color: rgba(255, 255, 255, 0.07);
- }
-
- &.selected {
- background-color: var(--primary-color);
- color: var(--text-primary);
- }
- }
+ .filter-icon {
+ flex-shrink: 0;
}
}
diff --git a/src/styles/main.scss b/src/styles/main.scss
index 606503b..eefaa3d 100644
--- a/src/styles/main.scss
+++ b/src/styles/main.scss
@@ -23,6 +23,9 @@
/* Notification components */
@use 'components/notifications/index' as *;
+/* Icon components */
+@use 'components/icons/index' as *;
+
/* Common components */
@use 'components/common/index' as *;
diff --git a/src/types/svg.d.ts b/src/types/svg.d.ts
new file mode 100644
index 0000000..2734df9
--- /dev/null
+++ b/src/types/svg.d.ts
@@ -0,0 +1,32 @@
+///
+
+declare module '*.svg' {
+ import React from 'react'
+
+ export const ReactComponent: React.FunctionComponent<
+ React.SVGProps
& { title?: string }
+ >
+
+ const src: string
+ export default src
+}
+
+declare module '*.png' {
+ const content: string
+ export default content
+}
+
+declare module '*.jpg' {
+ const content: string
+ export default content
+}
+
+declare module '*.jpeg' {
+ const content: string
+ export default content
+}
+
+declare module '*.gif' {
+ const content: string
+ export default content
+}
\ No newline at end of file
diff --git a/tsconfig.app.json b/tsconfig.app.json
index 8540649..535f5e4 100644
--- a/tsconfig.app.json
+++ b/tsconfig.app.json
@@ -1,8 +1,8 @@
{
"compilerOptions": {
- "baseUrl": "./src",
+ "baseUrl": ".",
"paths": {
- "@/*": ["*"]
+ "@/*": ["src/*"]
},
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
diff --git a/vite.config.ts b/vite.config.ts
index eb60b64..ecf0751 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,6 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
+import svgr from "vite-plugin-svgr";
// https://vitejs.dev/config/
export default defineConfig({
@@ -10,7 +11,19 @@ export default defineConfig({
},
},
- plugins: [react()],
+ plugins: [
+ react(),
+ svgr({
+ svgrOptions: {
+ // SVGR options go here
+ icon: true,
+ dimensions: false,
+ titleProp: true,
+ exportType: 'named',
+ },
+ include: '**/*.svg',
+ }),
+ ],
clearScreen: false,
server: {
@@ -23,4 +36,4 @@ export default defineConfig({
minify: 'esbuild',
sourcemap: true,
},
-})
+})
\ No newline at end of file