From 05f9c3198ca42087971903b4d7ee3dbd58309269 Mon Sep 17 00:00:00 2001 From: antigravity-xd Date: Sat, 11 Apr 2026 01:18:17 +0300 Subject: [PATCH] feat(ui): Implement M3 FAB and finalize core UI rework (Stage 3) - Implemented M3 Floating Action Button (FAB) with extended variant support - Integrated FAB into Navigation Rail (top placement) and Bottom Bar (floating) - Added user profile header to expanded sidebar - Polished M3 styles and transitions - Fixed various CSS and layout issues Refs #3 Co-Authored-By: Claude --- src/components/FAB.tsx | 24 ++++++++++++ src/components/Navigation.tsx | 44 ++++++++++++++-------- src/styles/globals.css | 69 +++++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 15 deletions(-) create mode 100644 src/components/FAB.tsx diff --git a/src/components/FAB.tsx b/src/components/FAB.tsx new file mode 100644 index 0000000..fcf9ac8 --- /dev/null +++ b/src/components/FAB.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { Plus } from 'lucide-react'; + +interface FABProps { + onClick?: () => void; + label?: string; + extended?: boolean; +} + +export const FAB: React.FC = ({ onClick, label, extended }) => { + return ( + + ); +}; diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx index 7948e02..ff5f3ed 100644 --- a/src/components/Navigation.tsx +++ b/src/components/Navigation.tsx @@ -14,6 +14,7 @@ import { import { useStore } from '../store/useStore'; import { useDisplay } from '../hooks/useDisplay'; import { useScrollDirection } from '../hooks/useScrollDirection'; +import { FAB } from './FAB'; interface NavItem { path: string; @@ -88,21 +89,26 @@ export const Navigation: React.FC = () => { if (navMode === 'bar') { return ( - + <> +
+ alert('Новая заявка')} label="Создать заявку" /> +
+ + ); } @@ -117,6 +123,14 @@ export const Navigation: React.FC = () => { {isExpanded && Bonch} +
+ alert('Новая заявка')} + label="Создать заявку" + extended={isExpanded} + /> +
+ {isExpanded && profile && (
diff --git a/src/styles/globals.css b/src/styles/globals.css index d831b2b..35addb2 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -142,6 +142,62 @@ body { margin-bottom: 16px; } +/* M3 FAB */ + +.m3-fab { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 56px; + height: 56px; + border-radius: 16px; + border: none; + background-color: var(--md-sys-color-primary-container); + color: var(--md-sys-color-on-primary-container); + box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12); + cursor: pointer; + position: relative; + overflow: hidden; + transition: box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1); +} + +.m3-fab:hover { + box-shadow: 0 2px 4px -1px rgba(0,0,0,.2), 0 4px 5px 0 rgba(0,0,0,.14), 0 1px 10px 0 rgba(0,0,0,.12); +} + +.m3-fab.extended { + padding: 0 16px; + width: auto; + gap: 12px; +} + +.m3-fab-icon { + display: flex; + align-items: center; + justify-content: center; +} + +.m3-fab-label { + font-size: 14px; + font-weight: 500; + letter-spacing: 0.1px; +} + +.m3-fab-state-layer { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: var(--md-sys-color-on-primary-container); + opacity: 0; + transition: opacity 0.2s; +} + +.m3-fab:hover .m3-fab-state-layer { + opacity: 0.08; +} + /* M3 Filled Text Field */ .m3-text-field-container { @@ -269,6 +325,19 @@ body { transform: translateY(100%); } +.mobile-fab-container { + position: fixed; + right: 16px; + bottom: calc(var(--nav-bar-height) + 16px); + z-index: 99; + transition: transform 0.2s ease-in-out, opacity 0.2s; +} + +.mobile-fab-container.hidden { + transform: translateY(calc(var(--nav-bar-height) + 16px)); + opacity: 0; +} + @media (min-width: 600px) { .navigation-bar { display: none; } .main-content { margin-left: var(--nav-rail-width); }