@n3wth/ui 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -27
- package/dist/atoms/Button/Button.d.ts +9 -1
- package/dist/atoms/Button/Button.d.ts.map +1 -1
- package/dist/atoms/Button/index.d.ts +1 -1
- package/dist/atoms/Button/index.d.ts.map +1 -1
- package/dist/atoms/Shape/Shape.d.ts +28 -2
- package/dist/atoms/Shape/Shape.d.ts.map +1 -1
- package/dist/atoms/Shape/index.d.ts +1 -1
- package/dist/atoms/Shape/index.d.ts.map +1 -1
- package/dist/hooks/index.d.ts +3 -2
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/useMediaQuery.d.ts +53 -1
- package/dist/hooks/useMediaQuery.d.ts.map +1 -1
- package/dist/hooks/useReducedMotion.d.ts +41 -0
- package/dist/hooks/useReducedMotion.d.ts.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2057 -5054
- package/dist/index.js.map +1 -0
- package/dist/molecules/NavLink/NavLink.d.ts.map +1 -1
- package/dist/organisms/Nav/Nav.d.ts +2 -1
- package/dist/organisms/Nav/Nav.d.ts.map +1 -1
- package/dist/r/button.json +15 -0
- package/dist/r/card.json +12 -0
- package/dist/r/hero.json +12 -0
- package/dist/r/registry.json +38 -0
- package/dist/styles.css +342 -4
- package/package.json +3 -2
- package/public/r/button.json +15 -0
- package/public/r/card.json +12 -0
- package/public/r/hero.json +12 -0
- package/public/r/registry.json +38 -0
- package/tailwind.preset.js +32 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NavLink.d.ts","sourceRoot":"","sources":["../../../src/molecules/NavLink/NavLink.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,oBAAoB,EAAE,KAAK,SAAS,EAAc,MAAM,OAAO,CAAA;AAG7E,MAAM,WAAW,YAAa,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;IAC3E,OAAO,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,MAAM,CAAA;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,eAAO,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"NavLink.d.ts","sourceRoot":"","sources":["../../../src/molecules/NavLink/NavLink.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,oBAAoB,EAAE,KAAK,SAAS,EAAc,MAAM,OAAO,CAAA;AAG7E,MAAM,WAAW,YAAa,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;IAC3E,OAAO,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,MAAM,CAAA;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,eAAO,MAAM,OAAO,4GAuCnB,CAAA"}
|
|
@@ -13,6 +13,7 @@ export interface NavProps extends HTMLAttributes<HTMLElement> {
|
|
|
13
13
|
onThemeToggle?: () => void;
|
|
14
14
|
showThemeToggle?: boolean;
|
|
15
15
|
fixed?: boolean;
|
|
16
|
+
hideOnScroll?: boolean;
|
|
16
17
|
}
|
|
17
|
-
export declare function Nav({ logo, logoHref, items, theme, onThemeToggle, showThemeToggle, fixed, className, ...props }: NavProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export declare function Nav({ logo, logoHref, items, theme, onThemeToggle, showThemeToggle, fixed, hideOnScroll, className, ...props }: NavProps): import("react/jsx-runtime").JSX.Element;
|
|
18
19
|
//# sourceMappingURL=Nav.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Nav.d.ts","sourceRoot":"","sources":["../../../src/organisms/Nav/Nav.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,SAAS,
|
|
1
|
+
{"version":3,"file":"Nav.d.ts","sourceRoot":"","sources":["../../../src/organisms/Nav/Nav.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,SAAS,EAA4C,MAAM,OAAO,CAAA;AAMrG,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,QAAS,SAAQ,cAAc,CAAC,WAAW,CAAC;IAC3D,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,OAAO,EAAE,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAA;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,wBAAgB,GAAG,CAAC,EAClB,IAAI,EACJ,QAAc,EACd,KAAU,EACV,KAAc,EACd,aAAa,EACb,eAAsB,EACtB,KAAa,EACb,YAAoB,EACpB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,QAAQ,2CAoLV"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "button",
|
|
4
|
+
"dependencies": [
|
|
5
|
+
"lucide-react"
|
|
6
|
+
],
|
|
7
|
+
"files": [
|
|
8
|
+
{
|
|
9
|
+
"path": "src/atoms/Button/Button.tsx",
|
|
10
|
+
"content": "import {\n forwardRef,\n type ButtonHTMLAttributes,\n type ReactNode,\n cloneElement,\n isValidElement,\n} from 'react'\nimport { cn } from '../../utils/cn'\n\nexport type ButtonSize = 'sm' | 'md' | 'lg'\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: 'primary' | 'secondary' | 'ghost' | 'glass'\n /** Size of the button. Can be a single value or responsive object */\n size?: ButtonSize | { base?: ButtonSize; md?: ButtonSize; lg?: ButtonSize }\n children: ReactNode\n isLoading?: boolean\n leftIcon?: ReactNode\n rightIcon?: ReactNode\n asChild?: boolean\n /** Ensures minimum 44px touch target for accessibility (WCAG 2.5.5) */\n touchTarget?: boolean\n}\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n variant = 'primary',\n size = 'md',\n children,\n isLoading = false,\n leftIcon,\n rightIcon,\n className,\n disabled,\n asChild = false,\n touchTarget = false,\n ...props\n },\n ref\n ) => {\n const baseStyles = [\n 'inline-flex items-center justify-center gap-2',\n 'font-medium',\n 'border',\n 'rounded-full',\n 'transition-[transform,background-color,border-color,color,opacity] duration-200 ease-out',\n 'focus-ring',\n 'disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none',\n // Active state for touch feedback\n 'active:scale-[0.96]',\n ]\n\n const variants = {\n primary: [\n 'bg-[var(--color-white)] text-[var(--color-bg)]',\n 'border-[var(--color-white)]',\n 'hover:scale-[1.02]',\n 'glow-white',\n ],\n secondary: [\n 'bg-transparent text-[var(--color-white)]',\n 'border-[var(--glass-border)]',\n 'hover:border-[var(--glass-highlight)] hover:bg-[var(--glass-bg)]',\n ],\n ghost: [\n 'bg-transparent text-[var(--color-grey-400)]',\n 'border-transparent',\n 'hover:text-[var(--color-white)] hover:bg-[var(--glass-bg)]',\n ],\n glass: [\n 'bg-[var(--glass-bg)] text-[var(--color-white)]',\n 'border-[var(--glass-border)]',\n 'backdrop-blur-lg',\n 'hover:bg-[rgba(255,255,255,0.1)] hover:border-[var(--glass-highlight)]',\n 'hover:scale-[1.02]',\n ],\n }\n\n const sizes = {\n sm: 'px-3 py-1.5 text-xs',\n md: 'px-4 py-2 text-sm',\n lg: 'px-6 py-3 text-base',\n }\n\n // Handle responsive size prop\n const getSizeClasses = () => {\n if (typeof size === 'string') {\n return sizes[size]\n }\n\n // Responsive size object\n const classes: string[] = []\n if (size.base) classes.push(sizes[size.base])\n if (size.md) classes.push(`md:${sizes[size.md].split(' ').join(' md:')}`)\n if (size.lg) classes.push(`lg:${sizes[size.lg].split(' ').join(' lg:')}`)\n\n // If no base specified, default to sm for mobile-first\n if (!size.base && (size.md || size.lg)) {\n classes.unshift(sizes.sm)\n }\n\n return classes.join(' ')\n }\n\n const touchTargetStyles = touchTarget ? 'min-w-[44px] min-h-[44px]' : ''\n\n const buttonClassName = cn(\n baseStyles,\n variants[variant],\n getSizeClasses(),\n touchTargetStyles,\n className\n )\n\n type ChildProps = { children?: ReactNode; className?: string }\n const childElement = isValidElement<ChildProps>(children) ? children : null\n\n const content = (\n <>\n {isLoading ? (\n <span className=\"w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin\" />\n ) : (\n leftIcon\n )}\n {asChild && childElement ? childElement.props.children : children}\n {!isLoading && rightIcon}\n </>\n )\n\n if (asChild && childElement) {\n return cloneElement(childElement, {\n className: cn(buttonClassName, childElement.props.className),\n ref,\n children: content,\n } as ChildProps)\n }\n\n return (\n <button\n ref={ref}\n className={buttonClassName}\n disabled={disabled || isLoading}\n {...props}\n >\n {content}\n </button>\n )\n }\n)\n\nButton.displayName = 'Button'\n",
|
|
11
|
+
"type": "registry:ui"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"type": "registry:ui"
|
|
15
|
+
}
|
package/dist/r/card.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "card",
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "src/molecules/Card/Card.tsx",
|
|
7
|
+
"content": "import { type HTMLAttributes, type ReactNode } from 'react'\nimport { cn } from '../../utils/cn'\n\nexport interface CardProps extends HTMLAttributes<HTMLDivElement> {\n variant?: 'default' | 'glass' | 'interactive'\n padding?: 'none' | 'sm' | 'md' | 'lg'\n children: ReactNode\n}\n\nexport function Card({\n variant = 'default',\n padding = 'md',\n children,\n className,\n ...props\n}: CardProps) {\n const baseStyles = [\n 'rounded-2xl',\n 'border',\n 'transition-[background-color,border-color] duration-300 ease-out',\n ]\n\n const variants = {\n default: [\n 'bg-transparent',\n 'border-[var(--glass-border)]',\n ],\n glass: [\n 'bg-[var(--glass-bg)]',\n 'backdrop-blur-lg',\n 'border-[var(--glass-border)]',\n ],\n interactive: [\n 'bg-transparent',\n 'border-[var(--glass-border)]',\n 'hover:border-[var(--glass-highlight)]',\n 'hover:bg-[var(--glass-bg)]',\n 'cursor-pointer',\n 'gradient-border shine-sweep',\n ],\n }\n\n const paddings = {\n none: '',\n sm: 'p-3',\n md: 'p-5',\n lg: 'p-8',\n }\n\n return (\n <div\n className={cn(baseStyles, variants[variant], paddings[padding], className)}\n {...props}\n >\n {children}\n </div>\n )\n}\n\nexport interface CardHeaderProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode\n}\n\nexport function CardHeader({ children, className, ...props }: CardHeaderProps) {\n return (\n <div className={cn('flex flex-col gap-1.5', className)} {...props}>\n {children}\n </div>\n )\n}\n\nexport interface CardTitleProps extends HTMLAttributes<HTMLHeadingElement> {\n children: ReactNode\n as?: 'h1' | 'h2' | 'h3' | 'h4'\n}\n\nexport function CardTitle({ children, as: Tag = 'h3', className, ...props }: CardTitleProps) {\n return (\n <Tag\n className={cn(\n 'font-display text-base font-semibold text-[var(--color-white)]',\n 'tracking-tight',\n className\n )}\n {...props}\n >\n {children}\n </Tag>\n )\n}\n\nexport interface CardDescriptionProps extends HTMLAttributes<HTMLParagraphElement> {\n children: ReactNode\n}\n\nexport function CardDescription({ children, className, ...props }: CardDescriptionProps) {\n return (\n <p\n className={cn(\n 'text-sm text-[var(--color-grey-400)]',\n 'line-clamp-2',\n className\n )}\n {...props}\n >\n {children}\n </p>\n )\n}\n\nexport interface CardContentProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode\n}\n\nexport function CardContent({ children, className, ...props }: CardContentProps) {\n return (\n <div className={cn('mt-4', className)} {...props}>\n {children}\n </div>\n )\n}\n\nexport interface CardFooterProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode\n}\n\nexport function CardFooter({ children, className, ...props }: CardFooterProps) {\n return (\n <div\n className={cn(\n 'mt-4 pt-4',\n 'border-t border-[var(--glass-border)]',\n 'flex items-center gap-3',\n className\n )}\n {...props}\n >\n {children}\n </div>\n )\n}\n",
|
|
8
|
+
"type": "registry:ui"
|
|
9
|
+
}
|
|
10
|
+
],
|
|
11
|
+
"type": "registry:ui"
|
|
12
|
+
}
|
package/dist/r/hero.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "hero",
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "src/organisms/Hero/Hero.tsx",
|
|
7
|
+
"content": "import { type HTMLAttributes, type ReactNode } from 'react'\nimport { cn } from '../../utils/cn'\nimport { Badge } from '../../atoms/Badge'\nimport { Button } from '../../atoms/Button'\n\nexport interface HeroCTA {\n label: string\n href: string\n variant?: 'primary' | 'secondary' | 'ghost'\n}\n\nexport interface HeroProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {\n badge?: string\n title: ReactNode\n description?: ReactNode\n ctas?: HeroCTA[]\n align?: 'left' | 'center'\n size?: 'default' | 'large'\n gradient?: boolean\n}\n\nexport function Hero({\n badge,\n title,\n description,\n ctas = [],\n align = 'center',\n size = 'default',\n gradient = true,\n className,\n ...props\n}: HeroProps) {\n const alignments = {\n left: 'text-left items-start',\n center: 'text-center items-center',\n }\n\n const titleSizes = {\n default: 'text-4xl sm:text-5xl md:text-6xl',\n large: 'text-5xl sm:text-6xl md:text-7xl lg:text-8xl',\n }\n\n return (\n <section\n className={cn(\n 'relative',\n 'px-6 py-24 md:py-32 lg:py-40',\n className\n )}\n {...props}\n >\n <div\n className={cn(\n 'mx-auto max-w-4xl',\n 'flex flex-col gap-6',\n alignments[align]\n )}\n >\n {badge && (\n <Badge variant=\"default\" size=\"md\" className=\"animate-in\">\n {badge}\n </Badge>\n )}\n\n <h1\n className={cn(\n 'font-display font-semibold tracking-tight',\n titleSizes[size],\n gradient ? 'hero-gradient-text' : 'text-[var(--color-white)]',\n 'animate-in'\n )}\n style={{ animationDelay: '0.1s', textAlign: align === 'center' ? 'center' : 'left', width: '100%', display: 'block' }}\n >\n {title}\n </h1>\n\n {description && (\n <p\n className={cn(\n 'text-lg md:text-xl',\n 'text-[var(--color-grey-400)]',\n 'max-w-2xl leading-relaxed',\n 'animate-in'\n )}\n style={{ animationDelay: '0.2s' }}\n >\n {description}\n </p>\n )}\n\n {ctas.length > 0 && (\n <div\n className={cn(\n 'flex flex-wrap gap-4 mt-4',\n align === 'center' ? 'justify-center' : 'justify-start',\n 'animate-in'\n )}\n style={{ animationDelay: '0.3s' }}\n >\n {ctas.map((cta, index) => (\n <Button\n key={cta.href}\n variant={cta.variant || (index === 0 ? 'primary' : 'secondary')}\n size=\"lg\"\n asChild\n >\n <a href={cta.href}>{cta.label}</a>\n </Button>\n ))}\n </div>\n )}\n </div>\n </section>\n )\n}\n",
|
|
8
|
+
"type": "registry:ui"
|
|
9
|
+
}
|
|
10
|
+
],
|
|
11
|
+
"type": "registry:ui"
|
|
12
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry.json",
|
|
3
|
+
"name": "n3wth-ui",
|
|
4
|
+
"homepage": "https://ui.newth.ai",
|
|
5
|
+
"items": [
|
|
6
|
+
{
|
|
7
|
+
"name": "button",
|
|
8
|
+
"type": "registry:ui",
|
|
9
|
+
"dependencies": ["lucide-react"],
|
|
10
|
+
"files": [
|
|
11
|
+
{
|
|
12
|
+
"path": "src/atoms/Button/Button.tsx",
|
|
13
|
+
"type": "registry:ui"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "card",
|
|
19
|
+
"type": "registry:ui",
|
|
20
|
+
"files": [
|
|
21
|
+
{
|
|
22
|
+
"path": "src/molecules/Card/Card.tsx",
|
|
23
|
+
"type": "registry:ui"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "hero",
|
|
29
|
+
"type": "registry:ui",
|
|
30
|
+
"files": [
|
|
31
|
+
{
|
|
32
|
+
"path": "src/organisms/Hero/Hero.tsx",
|
|
33
|
+
"type": "registry:ui"
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
package/dist/styles.css
CHANGED
|
@@ -213,6 +213,25 @@ body {
|
|
|
213
213
|
font-variant-numeric: tabular-nums;
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
/* Apple-style text shadow for readability over shapes/backgrounds */
|
|
217
|
+
.text-glow {
|
|
218
|
+
text-shadow: 0 2px 20px rgba(0, 0, 0, 0.5),
|
|
219
|
+
0 1px 3px rgba(0, 0, 0, 0.3);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.text-glow-subtle {
|
|
223
|
+
text-shadow: 0 1px 12px rgba(0, 0, 0, 0.4);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
[data-theme='light'] .text-glow {
|
|
227
|
+
text-shadow: 0 2px 20px rgba(255, 255, 255, 0.5),
|
|
228
|
+
0 1px 3px rgba(255, 255, 255, 0.3);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
[data-theme='light'] .text-glow-subtle {
|
|
232
|
+
text-shadow: 0 1px 12px rgba(255, 255, 255, 0.4);
|
|
233
|
+
}
|
|
234
|
+
|
|
216
235
|
/* Hero gradient text animation - enhanced with more dimension */
|
|
217
236
|
.hero-gradient-text {
|
|
218
237
|
background: linear-gradient(
|
|
@@ -304,13 +323,11 @@ body {
|
|
|
304
323
|
|
|
305
324
|
/* Glass Nav */
|
|
306
325
|
.glass-nav {
|
|
307
|
-
background:
|
|
308
|
-
backdrop-filter: blur(20px) saturate(180%);
|
|
309
|
-
-webkit-backdrop-filter: blur(20px) saturate(180%);
|
|
326
|
+
background: var(--color-bg);
|
|
310
327
|
}
|
|
311
328
|
|
|
312
329
|
[data-theme='light'] .glass-nav {
|
|
313
|
-
background:
|
|
330
|
+
background: var(--color-bg);
|
|
314
331
|
}
|
|
315
332
|
|
|
316
333
|
/* Glass Pill */
|
|
@@ -875,6 +892,230 @@ body {
|
|
|
875
892
|
border-width: 0;
|
|
876
893
|
}
|
|
877
894
|
|
|
895
|
+
/* ============================================================
|
|
896
|
+
Mobile-First Responsive Utilities
|
|
897
|
+
============================================================ */
|
|
898
|
+
|
|
899
|
+
/* Touch-friendly minimum target sizes (WCAG 2.5.5 AAA = 44px) */
|
|
900
|
+
.touch-target {
|
|
901
|
+
min-width: 44px;
|
|
902
|
+
min-height: 44px;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
.touch-target-sm {
|
|
906
|
+
min-width: 36px;
|
|
907
|
+
min-height: 36px;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/* Safe area insets for notched devices */
|
|
911
|
+
.safe-area-top {
|
|
912
|
+
padding-top: env(safe-area-inset-top);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
.safe-area-bottom {
|
|
916
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
.safe-area-left {
|
|
920
|
+
padding-left: env(safe-area-inset-left);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
.safe-area-right {
|
|
924
|
+
padding-right: env(safe-area-inset-right);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
.safe-area-inset {
|
|
928
|
+
padding-top: env(safe-area-inset-top);
|
|
929
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
930
|
+
padding-left: env(safe-area-inset-left);
|
|
931
|
+
padding-right: env(safe-area-inset-right);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/* Mobile viewport height (accounts for mobile browser chrome) */
|
|
935
|
+
.min-h-screen-mobile {
|
|
936
|
+
min-height: 100vh;
|
|
937
|
+
min-height: 100dvh;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
.h-screen-mobile {
|
|
941
|
+
height: 100vh;
|
|
942
|
+
height: 100dvh;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/* Disable hover effects on touch devices */
|
|
946
|
+
@media (hover: none) and (pointer: coarse) {
|
|
947
|
+
.glass-pill:hover,
|
|
948
|
+
.glass-card:hover,
|
|
949
|
+
.skill-card:hover,
|
|
950
|
+
.glow-white:hover,
|
|
951
|
+
.glow-accent:hover,
|
|
952
|
+
.link-hover:hover::after,
|
|
953
|
+
.gradient-border:hover::before,
|
|
954
|
+
.shine-sweep:hover::after {
|
|
955
|
+
transform: none;
|
|
956
|
+
box-shadow: none;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/* Use active states for touch feedback */
|
|
960
|
+
.glass-pill:active {
|
|
961
|
+
background: var(--glass-highlight);
|
|
962
|
+
transform: scale(0.98);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
.glass-card:active,
|
|
966
|
+
.skill-card:active {
|
|
967
|
+
background: var(--glass-bg);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
/* Disable shine sweep on touch */
|
|
971
|
+
.shine-sweep::after {
|
|
972
|
+
display: none;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
/* Improved tap highlight for iOS */
|
|
977
|
+
@supports (-webkit-touch-callout: none) {
|
|
978
|
+
.tap-highlight-none {
|
|
979
|
+
-webkit-tap-highlight-color: transparent;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
button,
|
|
983
|
+
a,
|
|
984
|
+
[role="button"] {
|
|
985
|
+
-webkit-tap-highlight-color: rgba(255, 255, 255, 0.1);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
[data-theme='light'] button,
|
|
989
|
+
[data-theme='light'] a,
|
|
990
|
+
[data-theme='light'] [role="button"] {
|
|
991
|
+
-webkit-tap-highlight-color: rgba(0, 0, 0, 0.1);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/* Mobile-specific text sizing for readability */
|
|
996
|
+
@media (max-width: 767px) {
|
|
997
|
+
.text-fluid-sm {
|
|
998
|
+
font-size: clamp(0.75rem, 2.5vw, 0.875rem);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
.text-fluid-base {
|
|
1002
|
+
font-size: clamp(0.875rem, 3vw, 1rem);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
.text-fluid-lg {
|
|
1006
|
+
font-size: clamp(1rem, 4vw, 1.25rem);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
.text-fluid-xl {
|
|
1010
|
+
font-size: clamp(1.25rem, 5vw, 1.5rem);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
.text-fluid-2xl {
|
|
1014
|
+
font-size: clamp(1.5rem, 6vw, 2rem);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
.text-fluid-3xl {
|
|
1018
|
+
font-size: clamp(1.875rem, 8vw, 3rem);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/* Prevent zoom on input focus (iOS) */
|
|
1022
|
+
input[type="text"],
|
|
1023
|
+
input[type="email"],
|
|
1024
|
+
input[type="password"],
|
|
1025
|
+
input[type="search"],
|
|
1026
|
+
input[type="tel"],
|
|
1027
|
+
input[type="url"],
|
|
1028
|
+
input[type="number"],
|
|
1029
|
+
select,
|
|
1030
|
+
textarea {
|
|
1031
|
+
font-size: 16px;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
/* Responsive spacing utilities */
|
|
1036
|
+
.gap-responsive {
|
|
1037
|
+
gap: 1rem;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
@media (min-width: 768px) {
|
|
1041
|
+
.gap-responsive {
|
|
1042
|
+
gap: 1.5rem;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
@media (min-width: 1024px) {
|
|
1047
|
+
.gap-responsive {
|
|
1048
|
+
gap: 2rem;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
.p-responsive {
|
|
1053
|
+
padding: 1rem;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
@media (min-width: 768px) {
|
|
1057
|
+
.p-responsive {
|
|
1058
|
+
padding: 1.5rem;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
@media (min-width: 1024px) {
|
|
1063
|
+
.p-responsive {
|
|
1064
|
+
padding: 2rem;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
/* Mobile container padding */
|
|
1069
|
+
.container-mobile {
|
|
1070
|
+
padding-left: 1rem;
|
|
1071
|
+
padding-right: 1rem;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
@media (min-width: 640px) {
|
|
1075
|
+
.container-mobile {
|
|
1076
|
+
padding-left: 1.5rem;
|
|
1077
|
+
padding-right: 1.5rem;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
@media (min-width: 768px) {
|
|
1082
|
+
.container-mobile {
|
|
1083
|
+
padding-left: 2rem;
|
|
1084
|
+
padding-right: 2rem;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
@media (min-width: 1024px) {
|
|
1089
|
+
.container-mobile {
|
|
1090
|
+
padding-left: 2.5rem;
|
|
1091
|
+
padding-right: 2.5rem;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/* Landscape orientation handling */
|
|
1096
|
+
@media (max-height: 500px) and (orientation: landscape) {
|
|
1097
|
+
.landscape-compact {
|
|
1098
|
+
padding-top: 0.5rem;
|
|
1099
|
+
padding-bottom: 0.5rem;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
.landscape-hidden {
|
|
1103
|
+
display: none;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/* Stack on mobile, row on larger screens */
|
|
1108
|
+
.stack-mobile {
|
|
1109
|
+
display: flex;
|
|
1110
|
+
flex-direction: column;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
@media (min-width: 768px) {
|
|
1114
|
+
.stack-mobile {
|
|
1115
|
+
flex-direction: row;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
878
1119
|
/* ============================================================
|
|
879
1120
|
Reduced Motion Preferences
|
|
880
1121
|
============================================================ */
|
|
@@ -963,4 +1204,101 @@ body {
|
|
|
963
1204
|
.connection-line {
|
|
964
1205
|
animation: none !important;
|
|
965
1206
|
}
|
|
1207
|
+
|
|
1208
|
+
/* Disable gradient animations */
|
|
1209
|
+
.gradient-border::before {
|
|
1210
|
+
transition: none !important;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
/* Disable glow effects */
|
|
1214
|
+
.glow-white,
|
|
1215
|
+
.glow-accent,
|
|
1216
|
+
.focus-glow:focus-within {
|
|
1217
|
+
box-shadow: none !important;
|
|
1218
|
+
transition: none !important;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
/* Disable shine sweep */
|
|
1222
|
+
.shine-sweep::after {
|
|
1223
|
+
display: none !important;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
/* Ensure instant state changes */
|
|
1227
|
+
button,
|
|
1228
|
+
a,
|
|
1229
|
+
[role="button"],
|
|
1230
|
+
.glass-pill,
|
|
1231
|
+
.glass-card,
|
|
1232
|
+
.skill-card,
|
|
1233
|
+
.copy-btn,
|
|
1234
|
+
.command-box {
|
|
1235
|
+
transition: none !important;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/* Keep functional focus indicators */
|
|
1239
|
+
.focus-ring:focus-visible {
|
|
1240
|
+
outline: 2px solid var(--color-accent);
|
|
1241
|
+
outline-offset: 2px;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/* ============================================================
|
|
1246
|
+
Responsive Shape Component
|
|
1247
|
+
============================================================ */
|
|
1248
|
+
|
|
1249
|
+
.shape-responsive {
|
|
1250
|
+
width: var(--shape-size-base, 48px);
|
|
1251
|
+
height: var(--shape-size-base, 48px);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
@media (min-width: 640px) {
|
|
1255
|
+
.shape-responsive {
|
|
1256
|
+
width: var(--shape-size-sm, var(--shape-size-base, 48px));
|
|
1257
|
+
height: var(--shape-size-sm, var(--shape-size-base, 48px));
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
@media (min-width: 768px) {
|
|
1262
|
+
.shape-responsive {
|
|
1263
|
+
width: var(--shape-size-md, var(--shape-size-sm, var(--shape-size-base, 48px)));
|
|
1264
|
+
height: var(--shape-size-md, var(--shape-size-sm, var(--shape-size-base, 48px)));
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
@media (min-width: 1024px) {
|
|
1269
|
+
.shape-responsive {
|
|
1270
|
+
width: var(--shape-size-lg, var(--shape-size-md, var(--shape-size-sm, var(--shape-size-base, 48px))));
|
|
1271
|
+
height: var(--shape-size-lg, var(--shape-size-md, var(--shape-size-sm, var(--shape-size-base, 48px))));
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
/* ============================================================
|
|
1276
|
+
High Contrast Mode Support
|
|
1277
|
+
============================================================ */
|
|
1278
|
+
|
|
1279
|
+
@media (prefers-contrast: high) {
|
|
1280
|
+
:root,
|
|
1281
|
+
[data-theme='dark'] {
|
|
1282
|
+
--glass-border: rgba(255, 255, 255, 0.3);
|
|
1283
|
+
--glass-highlight: rgba(255, 255, 255, 0.5);
|
|
1284
|
+
--color-grey-400: #a1a1a6;
|
|
1285
|
+
--color-grey-600: #8e8e93;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
[data-theme='light'] {
|
|
1289
|
+
--glass-border: rgba(0, 0, 0, 0.3);
|
|
1290
|
+
--glass-highlight: rgba(0, 0, 0, 0.5);
|
|
1291
|
+
--color-grey-400: #636366;
|
|
1292
|
+
--color-grey-600: #48484a;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
.glass-card,
|
|
1296
|
+
.glass-pill,
|
|
1297
|
+
.command-box {
|
|
1298
|
+
border-width: 2px;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
.focus-ring:focus-visible {
|
|
1302
|
+
outline-width: 3px;
|
|
1303
|
+
}
|
|
966
1304
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@n3wth/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Atomic design system for Newth sites - flat, minimal, iOS-inspired",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"build": "tsc && vite build && node -e \"const fs=require('fs');const css=fs.readFileSync('src/styles.css','utf8').replace(/@import 'tailwindcss';\\n\\n/,'');fs.writeFileSync('dist/styles.css',css)\"",
|
|
27
27
|
"lint": "eslint src",
|
|
28
28
|
"prepublishOnly": "npm run build",
|
|
29
|
-
"release:patch": "npm version patch -m 'chore: bump version to %s' && git push && git push --tags && gh release create v$(node -p \"require('./package.json').version\") --generate-notes",
|
|
29
|
+
"release:patch": "npm version patch -m 'chore: bump version to %s' && npm run registry:build && git add . && git commit -m 'chore: update registry' && git push && git push --tags && gh release create v$(node -p \"require('./package.json').version\") --generate-notes",
|
|
30
|
+
"registry:build": "npx shadcn@latest build",
|
|
30
31
|
"release:minor": "npm version minor -m 'chore: bump version to %s' && git push && git push --tags && gh release create v$(node -p \"require('./package.json').version\") --generate-notes",
|
|
31
32
|
"release:major": "npm version major -m 'chore: bump version to %s' && git push && git push --tags && gh release create v$(node -p \"require('./package.json').version\") --generate-notes"
|
|
32
33
|
},
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "button",
|
|
4
|
+
"dependencies": [
|
|
5
|
+
"lucide-react"
|
|
6
|
+
],
|
|
7
|
+
"files": [
|
|
8
|
+
{
|
|
9
|
+
"path": "src/atoms/Button/Button.tsx",
|
|
10
|
+
"content": "import {\n forwardRef,\n type ButtonHTMLAttributes,\n type ReactNode,\n cloneElement,\n isValidElement,\n} from 'react'\nimport { cn } from '../../utils/cn'\n\nexport type ButtonSize = 'sm' | 'md' | 'lg'\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: 'primary' | 'secondary' | 'ghost' | 'glass'\n /** Size of the button. Can be a single value or responsive object */\n size?: ButtonSize | { base?: ButtonSize; md?: ButtonSize; lg?: ButtonSize }\n children: ReactNode\n isLoading?: boolean\n leftIcon?: ReactNode\n rightIcon?: ReactNode\n asChild?: boolean\n /** Ensures minimum 44px touch target for accessibility (WCAG 2.5.5) */\n touchTarget?: boolean\n}\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n variant = 'primary',\n size = 'md',\n children,\n isLoading = false,\n leftIcon,\n rightIcon,\n className,\n disabled,\n asChild = false,\n touchTarget = false,\n ...props\n },\n ref\n ) => {\n const baseStyles = [\n 'inline-flex items-center justify-center gap-2',\n 'font-medium',\n 'border',\n 'rounded-full',\n 'transition-[transform,background-color,border-color,color,opacity] duration-200 ease-out',\n 'focus-ring',\n 'disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none',\n // Active state for touch feedback\n 'active:scale-[0.96]',\n ]\n\n const variants = {\n primary: [\n 'bg-[var(--color-white)] text-[var(--color-bg)]',\n 'border-[var(--color-white)]',\n 'hover:scale-[1.02]',\n 'glow-white',\n ],\n secondary: [\n 'bg-transparent text-[var(--color-white)]',\n 'border-[var(--glass-border)]',\n 'hover:border-[var(--glass-highlight)] hover:bg-[var(--glass-bg)]',\n ],\n ghost: [\n 'bg-transparent text-[var(--color-grey-400)]',\n 'border-transparent',\n 'hover:text-[var(--color-white)] hover:bg-[var(--glass-bg)]',\n ],\n glass: [\n 'bg-[var(--glass-bg)] text-[var(--color-white)]',\n 'border-[var(--glass-border)]',\n 'backdrop-blur-lg',\n 'hover:bg-[rgba(255,255,255,0.1)] hover:border-[var(--glass-highlight)]',\n 'hover:scale-[1.02]',\n ],\n }\n\n const sizes = {\n sm: 'px-3 py-1.5 text-xs',\n md: 'px-4 py-2 text-sm',\n lg: 'px-6 py-3 text-base',\n }\n\n // Handle responsive size prop\n const getSizeClasses = () => {\n if (typeof size === 'string') {\n return sizes[size]\n }\n\n // Responsive size object\n const classes: string[] = []\n if (size.base) classes.push(sizes[size.base])\n if (size.md) classes.push(`md:${sizes[size.md].split(' ').join(' md:')}`)\n if (size.lg) classes.push(`lg:${sizes[size.lg].split(' ').join(' lg:')}`)\n\n // If no base specified, default to sm for mobile-first\n if (!size.base && (size.md || size.lg)) {\n classes.unshift(sizes.sm)\n }\n\n return classes.join(' ')\n }\n\n const touchTargetStyles = touchTarget ? 'min-w-[44px] min-h-[44px]' : ''\n\n const buttonClassName = cn(\n baseStyles,\n variants[variant],\n getSizeClasses(),\n touchTargetStyles,\n className\n )\n\n type ChildProps = { children?: ReactNode; className?: string }\n const childElement = isValidElement<ChildProps>(children) ? children : null\n\n const content = (\n <>\n {isLoading ? (\n <span className=\"w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin\" />\n ) : (\n leftIcon\n )}\n {asChild && childElement ? childElement.props.children : children}\n {!isLoading && rightIcon}\n </>\n )\n\n if (asChild && childElement) {\n return cloneElement(childElement, {\n className: cn(buttonClassName, childElement.props.className),\n ref,\n children: content,\n } as ChildProps)\n }\n\n return (\n <button\n ref={ref}\n className={buttonClassName}\n disabled={disabled || isLoading}\n {...props}\n >\n {content}\n </button>\n )\n }\n)\n\nButton.displayName = 'Button'\n",
|
|
11
|
+
"type": "registry:ui"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"type": "registry:ui"
|
|
15
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "card",
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "src/molecules/Card/Card.tsx",
|
|
7
|
+
"content": "import { type HTMLAttributes, type ReactNode } from 'react'\nimport { cn } from '../../utils/cn'\n\nexport interface CardProps extends HTMLAttributes<HTMLDivElement> {\n variant?: 'default' | 'glass' | 'interactive'\n padding?: 'none' | 'sm' | 'md' | 'lg'\n children: ReactNode\n}\n\nexport function Card({\n variant = 'default',\n padding = 'md',\n children,\n className,\n ...props\n}: CardProps) {\n const baseStyles = [\n 'rounded-2xl',\n 'border',\n 'transition-[background-color,border-color] duration-300 ease-out',\n ]\n\n const variants = {\n default: [\n 'bg-transparent',\n 'border-[var(--glass-border)]',\n ],\n glass: [\n 'bg-[var(--glass-bg)]',\n 'backdrop-blur-lg',\n 'border-[var(--glass-border)]',\n ],\n interactive: [\n 'bg-transparent',\n 'border-[var(--glass-border)]',\n 'hover:border-[var(--glass-highlight)]',\n 'hover:bg-[var(--glass-bg)]',\n 'cursor-pointer',\n 'gradient-border shine-sweep',\n ],\n }\n\n const paddings = {\n none: '',\n sm: 'p-3',\n md: 'p-5',\n lg: 'p-8',\n }\n\n return (\n <div\n className={cn(baseStyles, variants[variant], paddings[padding], className)}\n {...props}\n >\n {children}\n </div>\n )\n}\n\nexport interface CardHeaderProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode\n}\n\nexport function CardHeader({ children, className, ...props }: CardHeaderProps) {\n return (\n <div className={cn('flex flex-col gap-1.5', className)} {...props}>\n {children}\n </div>\n )\n}\n\nexport interface CardTitleProps extends HTMLAttributes<HTMLHeadingElement> {\n children: ReactNode\n as?: 'h1' | 'h2' | 'h3' | 'h4'\n}\n\nexport function CardTitle({ children, as: Tag = 'h3', className, ...props }: CardTitleProps) {\n return (\n <Tag\n className={cn(\n 'font-display text-base font-semibold text-[var(--color-white)]',\n 'tracking-tight',\n className\n )}\n {...props}\n >\n {children}\n </Tag>\n )\n}\n\nexport interface CardDescriptionProps extends HTMLAttributes<HTMLParagraphElement> {\n children: ReactNode\n}\n\nexport function CardDescription({ children, className, ...props }: CardDescriptionProps) {\n return (\n <p\n className={cn(\n 'text-sm text-[var(--color-grey-400)]',\n 'line-clamp-2',\n className\n )}\n {...props}\n >\n {children}\n </p>\n )\n}\n\nexport interface CardContentProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode\n}\n\nexport function CardContent({ children, className, ...props }: CardContentProps) {\n return (\n <div className={cn('mt-4', className)} {...props}>\n {children}\n </div>\n )\n}\n\nexport interface CardFooterProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode\n}\n\nexport function CardFooter({ children, className, ...props }: CardFooterProps) {\n return (\n <div\n className={cn(\n 'mt-4 pt-4',\n 'border-t border-[var(--glass-border)]',\n 'flex items-center gap-3',\n className\n )}\n {...props}\n >\n {children}\n </div>\n )\n}\n",
|
|
8
|
+
"type": "registry:ui"
|
|
9
|
+
}
|
|
10
|
+
],
|
|
11
|
+
"type": "registry:ui"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "hero",
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "src/organisms/Hero/Hero.tsx",
|
|
7
|
+
"content": "import { type HTMLAttributes, type ReactNode } from 'react'\nimport { cn } from '../../utils/cn'\nimport { Badge } from '../../atoms/Badge'\nimport { Button } from '../../atoms/Button'\n\nexport interface HeroCTA {\n label: string\n href: string\n variant?: 'primary' | 'secondary' | 'ghost'\n}\n\nexport interface HeroProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {\n badge?: string\n title: ReactNode\n description?: ReactNode\n ctas?: HeroCTA[]\n align?: 'left' | 'center'\n size?: 'default' | 'large'\n gradient?: boolean\n}\n\nexport function Hero({\n badge,\n title,\n description,\n ctas = [],\n align = 'center',\n size = 'default',\n gradient = true,\n className,\n ...props\n}: HeroProps) {\n const alignments = {\n left: 'text-left items-start',\n center: 'text-center items-center',\n }\n\n const titleSizes = {\n default: 'text-4xl sm:text-5xl md:text-6xl',\n large: 'text-5xl sm:text-6xl md:text-7xl lg:text-8xl',\n }\n\n return (\n <section\n className={cn(\n 'relative',\n 'px-6 py-24 md:py-32 lg:py-40',\n className\n )}\n {...props}\n >\n <div\n className={cn(\n 'mx-auto max-w-4xl',\n 'flex flex-col gap-6',\n alignments[align]\n )}\n >\n {badge && (\n <Badge variant=\"default\" size=\"md\" className=\"animate-in\">\n {badge}\n </Badge>\n )}\n\n <h1\n className={cn(\n 'font-display font-semibold tracking-tight',\n titleSizes[size],\n gradient ? 'hero-gradient-text' : 'text-[var(--color-white)]',\n 'animate-in'\n )}\n style={{ animationDelay: '0.1s', textAlign: align === 'center' ? 'center' : 'left', width: '100%', display: 'block' }}\n >\n {title}\n </h1>\n\n {description && (\n <p\n className={cn(\n 'text-lg md:text-xl',\n 'text-[var(--color-grey-400)]',\n 'max-w-2xl leading-relaxed',\n 'animate-in'\n )}\n style={{ animationDelay: '0.2s' }}\n >\n {description}\n </p>\n )}\n\n {ctas.length > 0 && (\n <div\n className={cn(\n 'flex flex-wrap gap-4 mt-4',\n align === 'center' ? 'justify-center' : 'justify-start',\n 'animate-in'\n )}\n style={{ animationDelay: '0.3s' }}\n >\n {ctas.map((cta, index) => (\n <Button\n key={cta.href}\n variant={cta.variant || (index === 0 ? 'primary' : 'secondary')}\n size=\"lg\"\n asChild\n >\n <a href={cta.href}>{cta.label}</a>\n </Button>\n ))}\n </div>\n )}\n </div>\n </section>\n )\n}\n",
|
|
8
|
+
"type": "registry:ui"
|
|
9
|
+
}
|
|
10
|
+
],
|
|
11
|
+
"type": "registry:ui"
|
|
12
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry.json",
|
|
3
|
+
"name": "n3wth-ui",
|
|
4
|
+
"homepage": "https://ui.newth.ai",
|
|
5
|
+
"items": [
|
|
6
|
+
{
|
|
7
|
+
"name": "button",
|
|
8
|
+
"type": "registry:ui",
|
|
9
|
+
"dependencies": ["lucide-react"],
|
|
10
|
+
"files": [
|
|
11
|
+
{
|
|
12
|
+
"path": "src/atoms/Button/Button.tsx",
|
|
13
|
+
"type": "registry:ui"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "card",
|
|
19
|
+
"type": "registry:ui",
|
|
20
|
+
"files": [
|
|
21
|
+
{
|
|
22
|
+
"path": "src/molecules/Card/Card.tsx",
|
|
23
|
+
"type": "registry:ui"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "hero",
|
|
29
|
+
"type": "registry:ui",
|
|
30
|
+
"files": [
|
|
31
|
+
{
|
|
32
|
+
"path": "src/organisms/Hero/Hero.tsx",
|
|
33
|
+
"type": "registry:ui"
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
package/tailwind.preset.js
CHANGED
|
@@ -54,6 +54,38 @@ module.exports = {
|
|
|
54
54
|
'50%': { backgroundPosition: '100% 50%' },
|
|
55
55
|
},
|
|
56
56
|
},
|
|
57
|
+
// Mobile-first spacing scale
|
|
58
|
+
spacing: {
|
|
59
|
+
'safe-top': 'env(safe-area-inset-top)',
|
|
60
|
+
'safe-bottom': 'env(safe-area-inset-bottom)',
|
|
61
|
+
'safe-left': 'env(safe-area-inset-left)',
|
|
62
|
+
'safe-right': 'env(safe-area-inset-right)',
|
|
63
|
+
},
|
|
64
|
+
// Touch-friendly minimum sizes (WCAG 2.5.5)
|
|
65
|
+
minWidth: {
|
|
66
|
+
'touch': '44px',
|
|
67
|
+
'touch-sm': '36px',
|
|
68
|
+
},
|
|
69
|
+
minHeight: {
|
|
70
|
+
'touch': '44px',
|
|
71
|
+
'touch-sm': '36px',
|
|
72
|
+
'screen-dvh': '100dvh',
|
|
73
|
+
},
|
|
74
|
+
height: {
|
|
75
|
+
'screen-dvh': '100dvh',
|
|
76
|
+
},
|
|
77
|
+
// Fluid typography scale
|
|
78
|
+
fontSize: {
|
|
79
|
+
'fluid-xs': 'clamp(0.75rem, 2vw, 0.875rem)',
|
|
80
|
+
'fluid-sm': 'clamp(0.875rem, 2.5vw, 1rem)',
|
|
81
|
+
'fluid-base': 'clamp(1rem, 3vw, 1.125rem)',
|
|
82
|
+
'fluid-lg': 'clamp(1.125rem, 3.5vw, 1.25rem)',
|
|
83
|
+
'fluid-xl': 'clamp(1.25rem, 4vw, 1.5rem)',
|
|
84
|
+
'fluid-2xl': 'clamp(1.5rem, 5vw, 2rem)',
|
|
85
|
+
'fluid-3xl': 'clamp(1.875rem, 6vw, 2.5rem)',
|
|
86
|
+
'fluid-4xl': 'clamp(2.25rem, 8vw, 3.5rem)',
|
|
87
|
+
'fluid-5xl': 'clamp(3rem, 10vw, 4.5rem)',
|
|
88
|
+
},
|
|
57
89
|
},
|
|
58
90
|
},
|
|
59
91
|
}
|