refactor: flatten Microsoft skills from nested to flat directory structure
Rewrote sync_microsoft_skills.py (v4) to use each SKILL.md's frontmatter 'name' field as the flat directory name under skills/, replacing the nested skills/official/microsoft/<lang>/<category>/<service>/ hierarchy. This fixes CI failures caused by the indexing, validation, and catalog scripts expecting skills/<id>/SKILL.md (depth 1). Changes: - Rewrite scripts/sync_microsoft_skills.py for flat output with collision detection - Update scripts/tests/inspect_microsoft_repo.py for flat name mapping - Update scripts/tests/test_comprehensive_coverage.py for name uniqueness checks - Delete skills/official/ nested directory - Add 129 Microsoft skills as flat directories (e.g. skills/azure-mgmt-botservice-dotnet/) - Move attribution files to docs/ (LICENSE-MICROSOFT, microsoft-skills-attribution.json) - Rebuild skills_index.json, CATALOG.md, README.md (845 total skills)
This commit is contained in:
588
skills/frontend-ui-dark-ts/SKILL.md
Normal file
588
skills/frontend-ui-dark-ts/SKILL.md
Normal file
@@ -0,0 +1,588 @@
|
||||
---
|
||||
name: frontend-ui-dark-ts
|
||||
description: Build dark-themed React applications using Tailwind CSS with custom theming, glassmorphism effects, and Framer Motion animations. Use when creating dashboards, admin panels, or data-rich interfaces with a refined dark aesthetic.
|
||||
---
|
||||
|
||||
# Frontend UI Dark Theme (TypeScript)
|
||||
|
||||
A modern dark-themed React UI system using **Tailwind CSS** and **Framer Motion**. Designed for dashboards, admin panels, and data-rich applications with glassmorphism effects and tasteful animations.
|
||||
|
||||
## Stack
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| `react` | ^18.x | UI framework |
|
||||
| `react-dom` | ^18.x | DOM rendering |
|
||||
| `react-router-dom` | ^6.x | Routing |
|
||||
| `framer-motion` | ^11.x | Animations |
|
||||
| `clsx` | ^2.x | Class merging |
|
||||
| `tailwindcss` | ^3.x | Styling |
|
||||
| `vite` | ^5.x | Build tool |
|
||||
| `typescript` | ^5.x | Type safety |
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
npm create vite@latest my-app -- --template react-ts
|
||||
cd my-app
|
||||
npm install framer-motion clsx react-router-dom
|
||||
npm install -D tailwindcss postcss autoprefixer
|
||||
npx tailwindcss init -p
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
public/
|
||||
├── favicon.ico # Classic favicon (32x32)
|
||||
├── favicon.svg # Modern SVG favicon
|
||||
├── apple-touch-icon.png # iOS home screen (180x180)
|
||||
├── og-image.png # Social sharing image (1200x630)
|
||||
└── site.webmanifest # PWA manifest
|
||||
src/
|
||||
├── assets/
|
||||
│ └── fonts/
|
||||
│ ├── Segoe UI.ttf
|
||||
│ ├── Segoe UI Bold.ttf
|
||||
│ ├── Segoe UI Italic.ttf
|
||||
│ └── Segoe UI Bold Italic.ttf
|
||||
├── components/
|
||||
│ ├── ui/
|
||||
│ │ ├── Button.tsx
|
||||
│ │ ├── Card.tsx
|
||||
│ │ ├── Input.tsx
|
||||
│ │ ├── Badge.tsx
|
||||
│ │ ├── Dialog.tsx
|
||||
│ │ ├── Tabs.tsx
|
||||
│ │ └── index.ts
|
||||
│ └── layout/
|
||||
│ ├── AppShell.tsx
|
||||
│ ├── Sidebar.tsx
|
||||
│ └── PageHeader.tsx
|
||||
├── styles/
|
||||
│ └── globals.css
|
||||
├── App.tsx
|
||||
└── main.tsx
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### index.html
|
||||
|
||||
The HTML entry point with mobile viewport, favicons, and social meta tags:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" href="/favicon.ico" sizes="32x32" />
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
|
||||
<!-- Theme color for mobile browser chrome -->
|
||||
<meta name="theme-color" content="#18181B" />
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="App Name" />
|
||||
<meta property="og:description" content="App description" />
|
||||
<meta property="og:image" content="https://example.com/og-image.png" />
|
||||
<meta property="og:url" content="https://example.com" />
|
||||
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="App Name" />
|
||||
<meta name="twitter:description" content="App description" />
|
||||
<meta name="twitter:image" content="https://example.com/og-image.png" />
|
||||
|
||||
<title>App Name</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### public/site.webmanifest
|
||||
|
||||
PWA manifest for installable web apps:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "App Name",
|
||||
"short_name": "App",
|
||||
"icons": [
|
||||
{ "src": "/favicon.ico", "sizes": "32x32", "type": "image/x-icon" },
|
||||
{ "src": "/apple-touch-icon.png", "sizes": "180x180", "type": "image/png" }
|
||||
],
|
||||
"theme_color": "#18181B",
|
||||
"background_color": "#18181B",
|
||||
"display": "standalone"
|
||||
}
|
||||
```
|
||||
|
||||
### tailwind.config.js
|
||||
|
||||
```js
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Segoe UI', 'system-ui', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
brand: {
|
||||
DEFAULT: '#8251EE',
|
||||
hover: '#9366F5',
|
||||
light: '#A37EF5',
|
||||
subtle: 'rgba(130, 81, 238, 0.15)',
|
||||
},
|
||||
neutral: {
|
||||
bg1: 'hsl(240, 6%, 10%)',
|
||||
bg2: 'hsl(240, 5%, 12%)',
|
||||
bg3: 'hsl(240, 5%, 14%)',
|
||||
bg4: 'hsl(240, 4%, 18%)',
|
||||
bg5: 'hsl(240, 4%, 22%)',
|
||||
bg6: 'hsl(240, 4%, 26%)',
|
||||
},
|
||||
text: {
|
||||
primary: '#FFFFFF',
|
||||
secondary: '#A1A1AA',
|
||||
muted: '#71717A',
|
||||
},
|
||||
border: {
|
||||
subtle: 'hsla(0, 0%, 100%, 0.08)',
|
||||
DEFAULT: 'hsla(0, 0%, 100%, 0.12)',
|
||||
strong: 'hsla(0, 0%, 100%, 0.20)',
|
||||
},
|
||||
status: {
|
||||
success: '#10B981',
|
||||
warning: '#F59E0B',
|
||||
error: '#EF4444',
|
||||
info: '#3B82F6',
|
||||
},
|
||||
dataviz: {
|
||||
purple: '#8251EE',
|
||||
blue: '#3B82F6',
|
||||
green: '#10B981',
|
||||
yellow: '#F59E0B',
|
||||
red: '#EF4444',
|
||||
pink: '#EC4899',
|
||||
cyan: '#06B6D4',
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
DEFAULT: '0.5rem',
|
||||
lg: '0.75rem',
|
||||
xl: '1rem',
|
||||
},
|
||||
boxShadow: {
|
||||
glow: '0 0 20px rgba(130, 81, 238, 0.3)',
|
||||
'glow-lg': '0 0 40px rgba(130, 81, 238, 0.4)',
|
||||
},
|
||||
backdropBlur: {
|
||||
xs: '2px',
|
||||
},
|
||||
animation: {
|
||||
'fade-in': 'fadeIn 0.3s ease-out',
|
||||
'slide-up': 'slideUp 0.3s ease-out',
|
||||
'slide-down': 'slideDown 0.3s ease-out',
|
||||
},
|
||||
keyframes: {
|
||||
fadeIn: {
|
||||
'0%': { opacity: '0' },
|
||||
'100%': { opacity: '1' },
|
||||
},
|
||||
slideUp: {
|
||||
'0%': { opacity: '0', transform: 'translateY(10px)' },
|
||||
'100%': { opacity: '1', transform: 'translateY(0)' },
|
||||
},
|
||||
slideDown: {
|
||||
'0%': { opacity: '0', transform: 'translateY(-10px)' },
|
||||
'100%': { opacity: '1', transform: 'translateY(0)' },
|
||||
},
|
||||
},
|
||||
// Mobile: safe area insets for notched devices
|
||||
spacing: {
|
||||
'safe-top': 'env(safe-area-inset-top)',
|
||||
'safe-bottom': 'env(safe-area-inset-bottom)',
|
||||
'safe-left': 'env(safe-area-inset-left)',
|
||||
'safe-right': 'env(safe-area-inset-right)',
|
||||
},
|
||||
// Mobile: minimum touch target sizes (44px per Apple/Google guidelines)
|
||||
minHeight: {
|
||||
'touch': '44px',
|
||||
},
|
||||
minWidth: {
|
||||
'touch': '44px',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
```
|
||||
|
||||
### postcss.config.js
|
||||
|
||||
```js
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### src/styles/globals.css
|
||||
|
||||
```css
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Font faces */
|
||||
@font-face {
|
||||
font-family: 'Segoe UI';
|
||||
src: url('../assets/fonts/Segoe UI.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Segoe UI';
|
||||
src: url('../assets/fonts/Segoe UI Bold.ttf') format('truetype');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Segoe UI';
|
||||
src: url('../assets/fonts/Segoe UI Italic.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Segoe UI';
|
||||
src: url('../assets/fonts/Segoe UI Bold Italic.ttf') format('truetype');
|
||||
font-weight: 700;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* CSS Custom Properties */
|
||||
:root {
|
||||
/* Brand colors */
|
||||
--color-brand: #8251EE;
|
||||
--color-brand-hover: #9366F5;
|
||||
--color-brand-light: #A37EF5;
|
||||
--color-brand-subtle: rgba(130, 81, 238, 0.15);
|
||||
|
||||
/* Neutral backgrounds */
|
||||
--color-bg-1: hsl(240, 6%, 10%);
|
||||
--color-bg-2: hsl(240, 5%, 12%);
|
||||
--color-bg-3: hsl(240, 5%, 14%);
|
||||
--color-bg-4: hsl(240, 4%, 18%);
|
||||
--color-bg-5: hsl(240, 4%, 22%);
|
||||
--color-bg-6: hsl(240, 4%, 26%);
|
||||
|
||||
/* Text colors */
|
||||
--color-text-primary: #FFFFFF;
|
||||
--color-text-secondary: #A1A1AA;
|
||||
--color-text-muted: #71717A;
|
||||
|
||||
/* Border colors */
|
||||
--color-border-subtle: hsla(0, 0%, 100%, 0.08);
|
||||
--color-border-default: hsla(0, 0%, 100%, 0.12);
|
||||
--color-border-strong: hsla(0, 0%, 100%, 0.20);
|
||||
|
||||
/* Status colors */
|
||||
--color-success: #10B981;
|
||||
--color-warning: #F59E0B;
|
||||
--color-error: #EF4444;
|
||||
--color-info: #3B82F6;
|
||||
|
||||
/* Spacing */
|
||||
--spacing-xs: 0.25rem;
|
||||
--spacing-sm: 0.5rem;
|
||||
--spacing-md: 1rem;
|
||||
--spacing-lg: 1.5rem;
|
||||
--spacing-xl: 2rem;
|
||||
--spacing-2xl: 3rem;
|
||||
|
||||
/* Border radius */
|
||||
--radius-sm: 0.375rem;
|
||||
--radius-md: 0.5rem;
|
||||
--radius-lg: 0.75rem;
|
||||
--radius-xl: 1rem;
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: 150ms ease;
|
||||
--transition-normal: 200ms ease;
|
||||
--transition-slow: 300ms ease;
|
||||
}
|
||||
|
||||
/* Base styles */
|
||||
html {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-neutral-bg1 text-text-primary font-sans antialiased;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Focus styles */
|
||||
*:focus-visible {
|
||||
@apply outline-none ring-2 ring-brand ring-offset-2 ring-offset-neutral-bg1;
|
||||
}
|
||||
|
||||
/* Scrollbar styling */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
@apply bg-neutral-bg2;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
@apply bg-neutral-bg5 rounded-full;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
@apply bg-neutral-bg6;
|
||||
}
|
||||
|
||||
/* Glass utility classes */
|
||||
@layer components {
|
||||
.glass {
|
||||
@apply backdrop-blur-md bg-white/5 border border-white/10;
|
||||
}
|
||||
|
||||
.glass-card {
|
||||
@apply backdrop-blur-md bg-white/5 border border-white/10 rounded-xl;
|
||||
}
|
||||
|
||||
.glass-panel {
|
||||
@apply backdrop-blur-lg bg-black/40 border border-white/5;
|
||||
}
|
||||
|
||||
.glass-overlay {
|
||||
@apply backdrop-blur-sm bg-black/60;
|
||||
}
|
||||
|
||||
.glass-input {
|
||||
@apply backdrop-blur-sm bg-white/5 border border-white/10 focus:border-brand focus:bg-white/10;
|
||||
}
|
||||
}
|
||||
|
||||
/* Animation utilities */
|
||||
@layer utilities {
|
||||
.animate-in {
|
||||
animation: fadeIn 0.3s ease-out, slideUp 0.3s ease-out;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### src/main.tsx
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import App from './App';
|
||||
import './styles/globals.css';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>
|
||||
);
|
||||
```
|
||||
|
||||
### src/App.tsx
|
||||
|
||||
```tsx
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import { AppShell } from './components/layout/AppShell';
|
||||
import { Dashboard } from './pages/Dashboard';
|
||||
import { Settings } from './pages/Settings';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<AppShell>
|
||||
<AnimatePresence mode="wait">
|
||||
<Routes>
|
||||
<Route path="/" element={<Dashboard />} />
|
||||
<Route path="/settings" element={<Settings />} />
|
||||
</Routes>
|
||||
</AnimatePresence>
|
||||
</AppShell>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Animation Patterns
|
||||
|
||||
### Framer Motion Variants
|
||||
|
||||
```tsx
|
||||
// Fade in on mount
|
||||
export const fadeIn = {
|
||||
initial: { opacity: 0 },
|
||||
animate: { opacity: 1 },
|
||||
exit: { opacity: 0 },
|
||||
transition: { duration: 0.2 },
|
||||
};
|
||||
|
||||
// Slide up on mount
|
||||
export const slideUp = {
|
||||
initial: { opacity: 0, y: 20 },
|
||||
animate: { opacity: 1, y: 0 },
|
||||
exit: { opacity: 0, y: 20 },
|
||||
transition: { duration: 0.3, ease: 'easeOut' },
|
||||
};
|
||||
|
||||
// Scale on hover (for buttons/cards)
|
||||
export const scaleOnHover = {
|
||||
whileHover: { scale: 1.02 },
|
||||
whileTap: { scale: 0.98 },
|
||||
transition: { type: 'spring', stiffness: 400, damping: 17 },
|
||||
};
|
||||
|
||||
// Stagger children
|
||||
export const staggerContainer = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.05,
|
||||
delayChildren: 0.1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const staggerItem = {
|
||||
hidden: { opacity: 0, y: 10 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: { duration: 0.2, ease: 'easeOut' },
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Page Transition Wrapper
|
||||
|
||||
```tsx
|
||||
import { motion } from 'framer-motion';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
interface PageTransitionProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function PageTransition({ children }: PageTransitionProps) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
transition={{ duration: 0.3, ease: 'easeOut' }}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Glass Effect Patterns
|
||||
|
||||
### Glass Card
|
||||
|
||||
```tsx
|
||||
<div className="glass-card p-6">
|
||||
<h2 className="text-lg font-semibold text-text-primary">Card Title</h2>
|
||||
<p className="text-text-secondary mt-2">Card content goes here.</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Glass Panel (Sidebar)
|
||||
|
||||
```tsx
|
||||
<aside className="glass-panel w-64 h-screen p-4">
|
||||
<nav className="space-y-2">
|
||||
{/* Navigation items */}
|
||||
</nav>
|
||||
</aside>
|
||||
```
|
||||
|
||||
### Glass Modal Overlay
|
||||
|
||||
```tsx
|
||||
<motion.div
|
||||
className="fixed inset-0 glass-overlay flex items-center justify-center z-50"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
>
|
||||
<motion.div
|
||||
className="glass-card p-6 max-w-md w-full mx-4"
|
||||
initial={{ scale: 0.95, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
exit={{ scale: 0.95, opacity: 0 }}
|
||||
>
|
||||
{/* Modal content */}
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
```
|
||||
|
||||
## Typography
|
||||
|
||||
| Element | Classes |
|
||||
|---------|---------|
|
||||
| Page title | `text-2xl font-semibold text-text-primary` |
|
||||
| Section title | `text-lg font-semibold text-text-primary` |
|
||||
| Card title | `text-base font-medium text-text-primary` |
|
||||
| Body text | `text-sm text-text-secondary` |
|
||||
| Caption | `text-xs text-text-muted` |
|
||||
| Label | `text-sm font-medium text-text-secondary` |
|
||||
|
||||
## Color Usage
|
||||
|
||||
| Use Case | Color | Class |
|
||||
|----------|-------|-------|
|
||||
| Primary action | Brand purple | `bg-brand text-white` |
|
||||
| Primary hover | Brand hover | `hover:bg-brand-hover` |
|
||||
| Page background | Neutral bg1 | `bg-neutral-bg1` |
|
||||
| Card background | Neutral bg2 | `bg-neutral-bg2` |
|
||||
| Elevated surface | Neutral bg3 | `bg-neutral-bg3` |
|
||||
| Input background | Neutral bg2 | `bg-neutral-bg2` |
|
||||
| Input focus | Neutral bg3 | `focus:bg-neutral-bg3` |
|
||||
| Border default | Border default | `border-border` |
|
||||
| Border subtle | Border subtle | `border-border-subtle` |
|
||||
| Success | Status success | `text-status-success` |
|
||||
| Warning | Status warning | `text-status-warning` |
|
||||
| Error | Status error | `text-status-error` |
|
||||
|
||||
## Related Files
|
||||
|
||||
- [Design Tokens](./references/design-tokens.md) — Complete color system, spacing, typography scales
|
||||
- [Components](./references/components.md) — Button, Card, Input, Dialog, Tabs, and more
|
||||
- [Patterns](./references/patterns.md) — Page layouts, navigation, lists, forms
|
||||
Reference in New Issue
Block a user