feat: polish invoice editor and viewer UI with custom NumberInput

component

- Create custom NumberInput component with increment/decrement buttons
- Add 0.25 step increments for hours and rates in invoice forms
- Implement emerald-themed styling with hover states and accessibility
- Add keyboard navigation (arrow keys) and proper ARIA support
- Condense invoice editor tax/totals section into efficient grid layout
- Update client dropdown to single-line format (name + email)
- Add fixed footer with floating action bar pattern matching business
  forms
- Redesign invoice viewer with better space utilization and visual
  hierarchy
- Maintain professional appearance and consistent design system
- Fix Next.js 15 params Promise handling across all invoice pages
- Resolve TypeScript compilation errors and type-only imports
This commit is contained in:
2025-07-15 00:29:02 -04:00
parent 89de059501
commit f331136090
79 changed files with 9944 additions and 4223 deletions

View File

@@ -5,6 +5,9 @@
--font-sans:
var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-mono:
var(--font-azeret-mono), ui-monospace, SFMono-Regular, "SF Mono", Consolas,
"Liberation Mono", Menlo, monospace;
}
@theme inline {
@@ -48,11 +51,11 @@
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--background: oklch(0.99 0.003 164.25);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card: oklch(0.995 0.002 164.25);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover: oklch(0.995 0.002 164.25);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
@@ -80,6 +83,26 @@
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
/* Brand colors */
--brand-primary: oklch(0.646 0.222 164.25);
--brand-primary-hover: oklch(0.576 0.222 164.25);
--brand-secondary: oklch(0.6 0.118 184.704);
--brand-secondary-hover: oklch(0.53 0.118 184.704);
/* Status colors */
--status-success: oklch(0.646 0.222 164.25);
--status-success-foreground: oklch(0.985 0 0);
--status-success-muted: oklch(0.97 0.02 164.25);
--status-warning: oklch(0.828 0.189 84.429);
--status-warning-foreground: oklch(0.145 0 0);
--status-warning-muted: oklch(0.985 0.02 84.429);
--status-error: oklch(0.577 0.245 27.325);
--status-error-foreground: oklch(0.985 0 0);
--status-error-muted: oklch(0.985 0.02 27.325);
--status-info: oklch(0.6 0.118 184.704);
--status-info-foreground: oklch(0.985 0 0);
--status-info-muted: oklch(0.97 0.02 184.704);
}
@media (prefers-color-scheme: dark) {
@@ -116,6 +139,26 @@
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
/* Brand colors - dark mode */
--brand-primary: oklch(0.696 0.17 162.48);
--brand-primary-hover: oklch(0.766 0.17 162.48);
--brand-secondary: oklch(0.696 0.17 162.48);
--brand-secondary-hover: oklch(0.766 0.17 162.48);
/* Status colors - dark mode */
--status-success: oklch(0.696 0.17 162.48);
--status-success-foreground: oklch(0.145 0 0);
--status-success-muted: oklch(0.269 0.05 162.48);
--status-warning: oklch(0.828 0.189 84.429);
--status-warning-foreground: oklch(0.145 0 0);
--status-warning-muted: oklch(0.269 0.05 84.429);
--status-error: oklch(0.704 0.191 22.216);
--status-error-foreground: oklch(0.985 0 0);
--status-error-muted: oklch(0.269 0.05 22.216);
--status-info: oklch(0.769 0.188 70.08);
--status-info-foreground: oklch(0.145 0 0);
--status-info-muted: oklch(0.269 0.05 70.08);
}
}
@@ -128,7 +171,7 @@
@apply bg-background text-foreground font-sans antialiased;
}
/* Improved form elements for dark mode */
/* Comprehensive form elements styling - consistent across all inputs */
input[type="text"],
input[type="email"],
input[type="password"],
@@ -136,21 +179,111 @@
input[type="url"],
input[type="search"],
input[type="number"],
input[type="date"],
input[type="datetime-local"],
input[type="time"],
textarea,
select {
@apply bg-background text-foreground border-input;
@apply bg-background text-foreground border-input h-10 rounded-md px-3 py-2 text-sm shadow-xs transition-colors;
}
/* Textarea specific height override */
textarea {
@apply h-auto min-h-20 resize-y;
}
/* Placeholder styling */
input::placeholder,
textarea::placeholder {
@apply text-muted-foreground;
}
/* Better focus states */
/* Better focus states with consistent ring */
input:focus,
textarea:focus,
select:focus {
@apply ring-ring border-ring;
@apply ring-ring/50 border-ring ring-[3px] outline-none;
}
/* Disabled state styling */
input:disabled,
textarea:disabled,
select:disabled {
@apply cursor-not-allowed opacity-50;
}
/* Form input icons - consistent positioning for left icons */
.form-input-icon-left {
@apply pl-10;
}
.form-input-icon-left + .form-icon {
@apply text-muted-foreground pointer-events-none absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2;
}
/* Form section styling */
.form-section {
@apply space-y-6;
}
.form-section-header {
@apply flex items-center space-x-2;
}
.form-section-icon {
@apply text-primary h-5 w-5;
}
.form-section-title {
@apply text-foreground text-lg font-semibold;
}
/* Form field groups */
.form-field-group {
@apply space-y-2;
}
.form-field-label {
@apply text-foreground text-sm font-medium;
}
.form-field-help {
@apply text-muted-foreground text-xs;
}
/* Form grid layouts */
.form-grid-1 {
@apply grid grid-cols-1 gap-6;
}
.form-grid-2 {
@apply grid grid-cols-1 gap-6 md:grid-cols-2;
}
.form-grid-3 {
@apply grid grid-cols-1 gap-6 md:grid-cols-3;
}
/* Form buttons */
.form-actions {
@apply mt-8 flex justify-end gap-4;
}
/* Select elements specific styling to match inputs */
select {
@apply bg-background appearance-none;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
background-position: right 0.5rem center;
background-repeat: no-repeat;
background-size: 1.5em 1.5em;
padding-right: 2.5rem;
}
/* Dark mode select arrow */
@media (prefers-color-scheme: dark) {
select {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%9ca3af' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
}
}
/* Selection styling */
@@ -233,4 +366,141 @@
input[type="radio"]:checked {
@apply bg-primary border-primary;
}
/* Background gradient utilities that adapt to dark mode */
.bg-gradient-auth {
background: linear-gradient(135deg, #f0fdf4 0%, #d1fae5 100%);
}
@media (prefers-color-scheme: dark) {
.bg-gradient-auth {
background: linear-gradient(
135deg,
oklch(0.145 0 0) 0%,
oklch(0.185 0 0) 100%
);
}
}
.bg-gradient-dashboard {
background: linear-gradient(135deg, #ecfdf5 0%, #ffffff 40%, #f0fdfa 100%);
}
@media (prefers-color-scheme: dark) {
.bg-gradient-dashboard {
background: linear-gradient(
135deg,
oklch(0.145 0 0) 0%,
oklch(0.185 0 0) 40%,
oklch(0.205 0 0) 100%
);
}
}
/* Radial overlay that adapts to dark mode */
.bg-radial-overlay::before {
content: "";
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 0;
pointer-events: none;
background: radial-gradient(
ellipse at 80% 0%,
oklch(0.646 0.222 164.25 / 0.1) 0%,
transparent 60%
);
}
@media (prefers-color-scheme: dark) {
.bg-radial-overlay::before {
background: radial-gradient(
ellipse at 80% 0%,
oklch(0.696 0.17 162.48 / 0.15) 0%,
transparent 60%
);
}
}
/* Brand utility classes */
.bg-brand-primary {
background-color: var(--brand-primary);
color: var(--status-success-foreground);
}
.bg-brand-gradient {
background: linear-gradient(
135deg,
var(--brand-primary),
var(--brand-secondary)
);
color: white;
}
.hover\:bg-brand-gradient:hover {
background: linear-gradient(
135deg,
var(--brand-primary-hover),
var(--brand-secondary-hover)
);
}
.text-brand-primary {
color: var(--brand-primary);
}
.border-brand-primary {
border-color: var(--brand-primary);
}
/* Status utility classes */
.bg-status-success-muted {
background-color: var(--status-success-muted);
}
.bg-status-warning-muted {
background-color: var(--status-warning-muted);
}
.bg-status-error-muted {
background-color: var(--status-error-muted);
}
.bg-status-info-muted {
background-color: var(--status-info-muted);
}
.text-status-success {
color: var(--status-success);
}
.text-status-warning {
color: var(--status-warning);
}
.text-status-error {
color: var(--status-error);
}
.text-status-info {
color: var(--status-info);
}
.text-status-success-foreground {
color: var(--status-success-foreground);
}
.text-status-warning-foreground {
color: var(--status-warning-foreground);
}
.text-status-error-foreground {
color: var(--status-error-foreground);
}
.text-status-info-foreground {
color: var(--status-info-foreground);
}
}