@latte-macchiat-io/latte-vanilla-components 0.0.339 → 0.0.340
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 +70 -0
- package/package.json +1 -2
- package/src/components/Actions/index.tsx +3 -3
- package/src/components/Button/index.tsx +3 -3
- package/src/components/Carousel/index.tsx +3 -3
- package/src/components/Columns/index.tsx +3 -3
- package/src/components/ConsentCookie/index.tsx +2 -2
- package/src/components/Footer/index.tsx +3 -3
- package/src/components/Form/Row/index.tsx +2 -2
- package/src/components/Form/TextField/Input/index.tsx +2 -2
- package/src/components/Form/TextField/Label/index.tsx +3 -3
- package/src/components/Form/TextField/Textarea/index.tsx +3 -3
- package/src/components/Form/TextField/index.tsx +2 -2
- package/src/components/Form/index.tsx +2 -2
- package/src/components/Header/HeaderToggleNav/index.tsx +5 -5
- package/src/components/Header/index.tsx +2 -2
- package/src/components/Heading/index.tsx +2 -2
- package/src/components/Icon/index.tsx +2 -2
- package/src/components/KeyNumber/index.tsx +2 -2
- package/src/components/LanguageSwitcher/index.tsx +2 -2
- package/src/components/Logo/index.tsx +2 -2
- package/src/components/Main/index.tsx +3 -3
- package/src/components/Modal/index.tsx +2 -2
- package/src/components/Nav/index.tsx +3 -3
- package/src/components/NavLegal/index.tsx +3 -3
- package/src/components/NavSocial/index.tsx +2 -2
- package/src/components/Paragraph/index.tsx +2 -2
- package/src/components/Section/index.tsx +3 -3
- package/src/components/Video/index.tsx +2 -2
- package/src/index.ts +1 -0
- package/src/utils/styleOverride.ts +43 -0
package/README.md
CHANGED
|
@@ -295,6 +295,76 @@ const customButton = style({
|
|
|
295
295
|
});
|
|
296
296
|
```
|
|
297
297
|
|
|
298
|
+
### Overriding Component Styles
|
|
299
|
+
|
|
300
|
+
All components accept a `className` prop for customization. However, due to CSS cascade order in bundled applications (Next.js, Webpack, etc.), you need to use proper specificity to ensure your styles override library defaults.
|
|
301
|
+
|
|
302
|
+
#### Recommended: Use `styleOverride` Utility
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
import { Button, styleOverride } from '@latte-macchiat-io/latte-vanilla-components';
|
|
306
|
+
|
|
307
|
+
const customButton = styleOverride({
|
|
308
|
+
textTransform: 'uppercase',
|
|
309
|
+
letterSpacing: '0.1em',
|
|
310
|
+
backgroundColor: '#FF0000',
|
|
311
|
+
padding: '20px 40px',
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
<Button variant="primary" className={customButton}>
|
|
315
|
+
Custom Styled Button
|
|
316
|
+
</Button>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Why use `styleOverride`?** In production builds, CSS files are split into multiple chunks (`layout.css`, `page.css`, etc.). This can cause library styles to override your customizations even when your className appears last in the HTML. The `styleOverride` utility uses CSS attribute selectors to increase specificity from `(0,1,0)` to `(0,2,0)`, ensuring your styles always win.
|
|
320
|
+
|
|
321
|
+
#### Manual Override (Advanced)
|
|
322
|
+
|
|
323
|
+
If you prefer manual control:
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
import { style } from '@vanilla-extract/css';
|
|
327
|
+
|
|
328
|
+
const customButton = style({
|
|
329
|
+
selectors: {
|
|
330
|
+
'&[class]': {
|
|
331
|
+
// Your styles here - guaranteed to override library styles
|
|
332
|
+
backgroundColor: '#FF0000',
|
|
333
|
+
border: '2px solid #000',
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
#### Combining Class Names
|
|
340
|
+
|
|
341
|
+
Use the exported `cn` utility for conditional class names:
|
|
342
|
+
|
|
343
|
+
```tsx
|
|
344
|
+
import { cn, styleOverride } from '@latte-macchiat-io/latte-vanilla-components';
|
|
345
|
+
|
|
346
|
+
const baseButton = styleOverride({ padding: '20px' });
|
|
347
|
+
const activeButton = styleOverride({ backgroundColor: 'red' });
|
|
348
|
+
|
|
349
|
+
<Button className={cn(baseButton, isActive && activeButton)}>
|
|
350
|
+
Conditional Styles
|
|
351
|
+
</Button>
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
#### Troubleshooting Style Overrides
|
|
355
|
+
|
|
356
|
+
**Problem:** Custom styles aren't applying
|
|
357
|
+
|
|
358
|
+
**Solutions:**
|
|
359
|
+
1. Use `styleOverride` utility instead of plain `style()`
|
|
360
|
+
2. Check browser DevTools to see which CSS rule is winning
|
|
361
|
+
3. Verify your className appears in the rendered HTML
|
|
362
|
+
4. Clear build cache: `rm -rf .next && npm run dev`
|
|
363
|
+
|
|
364
|
+
**Problem:** Styles work in dev but not production
|
|
365
|
+
|
|
366
|
+
**Solution:** This is a CSS chunking issue. Using `styleOverride` ensures proper specificity regardless of load order.
|
|
367
|
+
|
|
298
368
|
## 🎯 Performance
|
|
299
369
|
|
|
300
370
|
- **Zero Runtime CSS-in-JS** - All styles compiled at build time
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@latte-macchiat-io/latte-vanilla-components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.340",
|
|
4
4
|
"description": "Beautiful components for amazing projects, with a touch of Vanilla 🥤",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -44,7 +44,6 @@
|
|
|
44
44
|
"@vanilla-extract/recipes": "^0.5.7",
|
|
45
45
|
"@vanilla-extract/sprinkles": "^1.6.5",
|
|
46
46
|
"@vanilla-extract/vite-plugin": "^5.1.1",
|
|
47
|
-
"clsx": "^2.1.1",
|
|
48
47
|
"eslint": "^9.15.0",
|
|
49
48
|
"eslint-config-prettier": "^10.0.1",
|
|
50
49
|
"eslint-import-resolver-typescript": "^4.4.4",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { clsx } from 'clsx';
|
|
2
|
-
|
|
3
1
|
import { actionsRecipe, type ActionsVariants } from './styles.css';
|
|
2
|
+
import { cn } from '../../utils/styleOverride';
|
|
3
|
+
|
|
4
4
|
|
|
5
5
|
export type ActionsProps = React.HTMLAttributes<HTMLDivElement> &
|
|
6
6
|
ActionsVariants & {
|
|
@@ -8,5 +8,5 @@ export type ActionsProps = React.HTMLAttributes<HTMLDivElement> &
|
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
export const Actions = ({ align, direction, className, children }: ActionsProps) => (
|
|
11
|
-
<div className={
|
|
11
|
+
<div className={cn(actionsRecipe({ align, direction }), className)}>{children}</div>
|
|
12
12
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { clsx } from 'clsx';
|
|
2
1
|
|
|
3
2
|
import { buttonRecipe, type ButtonVariants } from './styles.css';
|
|
4
3
|
import { AllowedButton } from './types';
|
|
4
|
+
import { cn } from '../../utils/styleOverride';
|
|
5
5
|
|
|
6
6
|
export type ButtonProps = React.HTMLAttributes<HTMLButtonElement | HTMLAnchorElement> &
|
|
7
7
|
ButtonVariants & {
|
|
@@ -30,14 +30,14 @@ export const Button = ({
|
|
|
30
30
|
}: ButtonProps) => {
|
|
31
31
|
if (as === 'a') {
|
|
32
32
|
return (
|
|
33
|
-
<a href={href} className={
|
|
33
|
+
<a href={href} className={cn(buttonRecipe({ variant, style, size, fullWidth }), className)}>
|
|
34
34
|
{children}
|
|
35
35
|
</a>
|
|
36
36
|
);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
return (
|
|
40
|
-
<button onClick={onClick} disabled={isDisabled || isPending} className={
|
|
40
|
+
<button onClick={onClick} disabled={isDisabled || isPending} className={cn(buttonRecipe({ variant, style, size, fullWidth }), className)}>
|
|
41
41
|
{isPending ? translation.isPendingLabel : children}
|
|
42
42
|
</button>
|
|
43
43
|
);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { clsx } from 'clsx';
|
|
4
3
|
import { ReactNode, useEffect, useRef, useState } from 'react';
|
|
5
4
|
|
|
6
5
|
import {
|
|
@@ -20,6 +19,7 @@ import {
|
|
|
20
19
|
|
|
21
20
|
import { breakpoints } from '../../styles/mediaqueries';
|
|
22
21
|
import { getResponsiveValue, parseResponsiveNumber } from '../../utils/getResponsiveValue';
|
|
22
|
+
import { cn } from '../../utils/styleOverride';
|
|
23
23
|
import { useWindowSize } from '../../utils/useWindowSize';
|
|
24
24
|
|
|
25
25
|
import { Icon } from '../Icon';
|
|
@@ -172,7 +172,7 @@ export const Carousel = ({
|
|
|
172
172
|
const maxIndex = Math.max(0, data.length - visibleItems);
|
|
173
173
|
|
|
174
174
|
return (
|
|
175
|
-
<div className={
|
|
175
|
+
<div className={cn(carouselRecipe({ isFullWidth }), className)}>
|
|
176
176
|
<div ref={carouselRef} className={carouselContent({ overflow })}>
|
|
177
177
|
<div
|
|
178
178
|
ref={slideRef}
|
|
@@ -208,7 +208,7 @@ export const Carousel = ({
|
|
|
208
208
|
type="button"
|
|
209
209
|
aria-label={`Go to slide ${index + 1}`}
|
|
210
210
|
onClick={() => handleBulletClick(index)}
|
|
211
|
-
className={
|
|
211
|
+
className={cn(carouselBullet, index === currentIndex && carouselBulletActive)}
|
|
212
212
|
/>
|
|
213
213
|
))}
|
|
214
214
|
</div>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { clsx } from 'clsx';
|
|
2
|
-
|
|
3
1
|
import { columnsRecipe, ColumnsVariants } from './styles.css';
|
|
2
|
+
import { cn } from '../../utils/styleOverride';
|
|
3
|
+
|
|
4
4
|
|
|
5
5
|
export type ColumnsProps = React.HTMLAttributes<HTMLDivElement> &
|
|
6
6
|
ColumnsVariants & {
|
|
@@ -12,7 +12,7 @@ export const Columns = ({ align, vAlign, columns, children, className }: Columns
|
|
|
12
12
|
const columnsValue = columns.map((c) => `${c}fr`).join(' ');
|
|
13
13
|
|
|
14
14
|
return (
|
|
15
|
-
<div className={
|
|
15
|
+
<div className={cn(columnsRecipe({ align, vAlign }), className)} style={{ ['--columns' as never]: columnsValue }}>
|
|
16
16
|
{children}
|
|
17
17
|
</div>
|
|
18
18
|
);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { clsx } from 'clsx';
|
|
4
3
|
import { useEffect, useState } from 'react';
|
|
5
4
|
|
|
6
5
|
import { getCookie, setCookie } from './cookie';
|
|
7
6
|
import { consentActions, consentContent, type ConsentCookieVariants, consentRecipe } from './styles.css';
|
|
7
|
+
import { cn } from '../../utils/styleOverride';
|
|
8
8
|
|
|
9
9
|
import { Button } from '../Button';
|
|
10
10
|
|
|
@@ -89,7 +89,7 @@ export const ConsentCookie = ({
|
|
|
89
89
|
if (!showConsent) return null;
|
|
90
90
|
|
|
91
91
|
return (
|
|
92
|
-
<div className={
|
|
92
|
+
<div className={cn(consentRecipe({ variant }), className)} role="dialog" aria-modal="true" aria-labelledby="consent-title">
|
|
93
93
|
<div className={consentContent}>
|
|
94
94
|
{children}
|
|
95
95
|
<div className={consentActions}>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { clsx } from 'clsx';
|
|
2
|
-
|
|
3
1
|
import { footerRecipe } from './styles.css';
|
|
2
|
+
import { cn } from '../../utils/styleOverride';
|
|
3
|
+
|
|
4
4
|
|
|
5
5
|
export type FooterProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
6
6
|
children: React.ReactNode;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
export const Footer = ({ children, className }: FooterProps) => <footer className={
|
|
9
|
+
export const Footer = ({ children, className }: FooterProps) => <footer className={cn(footerRecipe, className)}>{children}</footer>;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { clsx } from 'clsx';
|
|
2
1
|
import { forwardRef } from 'react';
|
|
3
2
|
|
|
4
3
|
import { rowRecipe, type RowVariants } from './styles.css';
|
|
4
|
+
import { cn } from '../../utils/styleOverride';
|
|
5
5
|
|
|
6
6
|
export type RowProps = React.HTMLAttributes<HTMLDivElement> & RowVariants;
|
|
7
7
|
|
|
8
8
|
export const Row = forwardRef<HTMLDivElement, RowProps>(({ children, align, className }, ref) => {
|
|
9
9
|
return (
|
|
10
|
-
<div ref={ref} className={
|
|
10
|
+
<div ref={ref} className={cn(rowRecipe({ align }), className)}>
|
|
11
11
|
{children}
|
|
12
12
|
</div>
|
|
13
13
|
);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { clsx } from 'clsx';
|
|
2
1
|
import { inputRecipe } from './styles.css';
|
|
3
2
|
|
|
4
3
|
import { InputType } from './types';
|
|
4
|
+
import { cn } from '../../../utils/styleOverride';
|
|
5
5
|
|
|
6
6
|
export type InputProps = React.InputHTMLAttributes<HTMLInputElement> & {
|
|
7
7
|
type?: InputType;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
export const Input = ({ name, type = 'text', value, required, placeholder, className }: InputProps) => (
|
|
11
|
-
<input id={name} name={name} type={type} value={value} required={required} placeholder={placeholder} className={
|
|
11
|
+
<input id={name} name={name} type={type} value={value} required={required} placeholder={placeholder} className={cn(inputRecipe(), className)} />
|
|
12
12
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { clsx } from 'clsx';
|
|
2
|
-
|
|
3
1
|
import { labelRecipe, type LabelVariants } from './styles.css';
|
|
2
|
+
import { cn } from '../../../utils/styleOverride';
|
|
3
|
+
|
|
4
4
|
|
|
5
5
|
export type LabelProps = React.LabelHTMLAttributes<HTMLLabelElement> &
|
|
6
6
|
LabelVariants & {
|
|
@@ -10,7 +10,7 @@ export type LabelProps = React.LabelHTMLAttributes<HTMLLabelElement> &
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
export const Label = ({ label, name, required, className }: LabelProps) => (
|
|
13
|
-
<label htmlFor={name} className={
|
|
13
|
+
<label htmlFor={name} className={cn(labelRecipe(), className)}>
|
|
14
14
|
{label} {required && '*'}
|
|
15
15
|
</label>
|
|
16
16
|
);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { clsx } from 'clsx';
|
|
2
|
-
|
|
3
1
|
import { textareaRecipe } from './styles.css';
|
|
2
|
+
import { cn } from '../../../utils/styleOverride';
|
|
3
|
+
|
|
4
4
|
|
|
5
5
|
export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
|
6
6
|
name: string;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
export const Textarea = ({ name, rows, className }: TextareaProps) => (
|
|
10
|
-
<textarea id={name} rows={rows} name={name} className={
|
|
10
|
+
<textarea id={name} rows={rows} name={name} className={cn(textareaRecipe(), className)} />
|
|
11
11
|
);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { clsx } from 'clsx';
|
|
4
3
|
import { useMemo } from 'react';
|
|
5
4
|
|
|
6
5
|
import { Input } from './Input';
|
|
@@ -8,6 +7,7 @@ import { InputType } from './Input/types';
|
|
|
8
7
|
|
|
9
8
|
import { errorMessage, messageContainer, textFieldRecipe } from './styles.css';
|
|
10
9
|
import { Textarea } from './Textarea';
|
|
10
|
+
import { cn } from '../../utils/styleOverride';
|
|
11
11
|
|
|
12
12
|
type CommonProps = {
|
|
13
13
|
name: string;
|
|
@@ -31,7 +31,7 @@ export const TextField = (props: TextFieldProps) => {
|
|
|
31
31
|
const isTextarea = type === 'textarea';
|
|
32
32
|
|
|
33
33
|
return (
|
|
34
|
-
<div className={
|
|
34
|
+
<div className={cn(textFieldRecipe(), className)}>
|
|
35
35
|
{label && <label htmlFor={name}>{label}</label>}
|
|
36
36
|
|
|
37
37
|
{isTextarea ? (
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { clsx } from 'clsx';
|
|
2
1
|
import { formRecipe } from './styles.css';
|
|
2
|
+
import { cn } from '../../utils/styleOverride';
|
|
3
3
|
|
|
4
4
|
export type FormProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
5
5
|
as?: 'form' | 'div';
|
|
@@ -8,5 +8,5 @@ export type FormProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
|
8
8
|
|
|
9
9
|
export const Form = ({ as = 'form', children, className }: FormProps) => {
|
|
10
10
|
const Component = as;
|
|
11
|
-
return <Component className={
|
|
11
|
+
return <Component className={cn(formRecipe(), className)}>{children}</Component>;
|
|
12
12
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { clsx } from 'clsx';
|
|
2
1
|
|
|
3
2
|
import { ReactNode } from 'react';
|
|
4
3
|
import { bar, toggleNavButton, toggleNavButtonMenu } from './styles.css';
|
|
4
|
+
import { cn } from '../../../utils/styleOverride';
|
|
5
5
|
|
|
6
6
|
export type HeaderToggleNavProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
7
7
|
isOpen?: boolean;
|
|
@@ -11,12 +11,12 @@ export type HeaderToggleNavProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
|
11
11
|
|
|
12
12
|
export const HeaderToggleNav = ({ isOpen, onClick, children, className }: HeaderToggleNavProps) => {
|
|
13
13
|
return (
|
|
14
|
-
<button onClick={onClick} aria-label="Toggle navigation" className={
|
|
14
|
+
<button onClick={onClick} aria-label="Toggle navigation" className={cn(toggleNavButton(), className)}>
|
|
15
15
|
{children || (
|
|
16
16
|
<span className={toggleNavButtonMenu()}>
|
|
17
|
-
<span className={
|
|
18
|
-
<span className={
|
|
19
|
-
<span className={
|
|
17
|
+
<span className={cn(bar, isOpen && 'open')} />
|
|
18
|
+
<span className={cn(bar, isOpen && 'open')} />
|
|
19
|
+
<span className={cn(bar, isOpen && 'open')} />
|
|
20
20
|
</span>
|
|
21
21
|
)}
|
|
22
22
|
</button>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import clsx from 'clsx';
|
|
4
3
|
import { useState } from 'react';
|
|
5
4
|
|
|
6
5
|
import { HeaderToggleNav } from './HeaderToggleNav';
|
|
7
6
|
import { headerOverlayRecipe, headerPlaceholder, headerRecipe, headerToggleNav, HeaderToggleNavVariants } from './styles.css';
|
|
7
|
+
import { cn } from '../../utils/styleOverride';
|
|
8
8
|
|
|
9
9
|
export type HeaderProps = React.HTMLAttributes<HTMLDivElement> &
|
|
10
10
|
HeaderToggleNavVariants & {
|
|
@@ -30,7 +30,7 @@ export const Header = ({ children, childrenOverlay, childrenToggleNav, isFixed =
|
|
|
30
30
|
|
|
31
31
|
return (
|
|
32
32
|
<>
|
|
33
|
-
<header className={
|
|
33
|
+
<header className={cn(headerRecipe({ isFixed }), className)}>
|
|
34
34
|
{childrenOverlay && <div className={headerOverlayRecipe({ isOpen: isNavOpen })}>{childrenOverlay}</div>}
|
|
35
35
|
|
|
36
36
|
{children}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { clsx } from 'clsx';
|
|
2
1
|
import parse from 'html-react-parser';
|
|
3
2
|
|
|
4
3
|
import { headingRecipe, HeadingVariants } from './styles.css';
|
|
5
4
|
import { AllowedHeading } from './types';
|
|
5
|
+
import { cn } from '../../utils/styleOverride';
|
|
6
6
|
|
|
7
7
|
export type HeadingProps = React.HTMLAttributes<HTMLHeadingElement> &
|
|
8
8
|
HeadingVariants & {
|
|
@@ -24,7 +24,7 @@ export const Heading = ({ as, size, className, children }: HeadingProps) => {
|
|
|
24
24
|
const resolvedSize = size ?? levelToSize[as];
|
|
25
25
|
|
|
26
26
|
return (
|
|
27
|
-
<Component className={
|
|
27
|
+
<Component className={cn(headingRecipe({ size: resolvedSize, as }), className)}>
|
|
28
28
|
{parse(typeof children === 'string' ? children : '')}
|
|
29
29
|
</Component>
|
|
30
30
|
);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import clsx from 'clsx';
|
|
2
1
|
|
|
3
2
|
import path from './path';
|
|
4
3
|
|
|
5
4
|
import { iconRecipe } from './style.css';
|
|
6
5
|
import { themeContract } from '../../theme/contract.css';
|
|
6
|
+
import { cn } from '../../utils/styleOverride';
|
|
7
7
|
|
|
8
8
|
export type IconProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
9
9
|
icon?: string;
|
|
@@ -17,7 +17,7 @@ export const Icon = ({ icon = '', className, size = 24, iconPath = '', viewBox =
|
|
|
17
17
|
const d = iconPath || path[icon as keyof typeof path];
|
|
18
18
|
|
|
19
19
|
return (
|
|
20
|
-
<svg className={
|
|
20
|
+
<svg className={cn(iconRecipe, className)} viewBox={viewBox} width={`${size}px`} height={`${size}px`}>
|
|
21
21
|
<path className={iconPath} d={d} fill={color} />
|
|
22
22
|
</svg>
|
|
23
23
|
);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { clsx } from 'clsx';
|
|
4
3
|
import { forwardRef } from 'react';
|
|
5
4
|
import CountUp from 'react-countup';
|
|
6
5
|
|
|
7
6
|
import { useInView } from 'react-intersection-observer';
|
|
8
7
|
|
|
9
8
|
import { keyNumberRecipe, keyNumberValueRecipe, KeyNumberValueVariants, type KeyNumberVariants } from './styles.css';
|
|
9
|
+
import { cn } from '../../utils/styleOverride';
|
|
10
10
|
|
|
11
11
|
export type KeyNumberProps = React.HTMLAttributes<HTMLDivElement> &
|
|
12
12
|
KeyNumberVariants &
|
|
@@ -39,7 +39,7 @@ export const KeyNumber = forwardRef<HTMLDivElement, KeyNumberProps>(
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
}}
|
|
42
|
-
className={
|
|
42
|
+
className={cn(keyNumberRecipe({ align, vAlign }), className)}>
|
|
43
43
|
{prefix}
|
|
44
44
|
<CountUp end={inView ? value : 0} duration={duration} separator={separator} decimals={decimals} className={keyNumberValueRecipe({ size })} />
|
|
45
45
|
{suffix}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { clsx } from 'clsx';
|
|
2
1
|
import { languageSwitcherIcon, languageSwitcherRecipe, languageSwitcherSelect, type LanguageSwitcherVariants } from './styles.css';
|
|
2
|
+
import { cn } from '../../utils/styleOverride';
|
|
3
3
|
|
|
4
4
|
import { Icon } from '../Icon';
|
|
5
5
|
|
|
@@ -31,7 +31,7 @@ export const LanguageSwitcher = ({
|
|
|
31
31
|
const handleSelectChange = (event: React.ChangeEvent<HTMLSelectElement>) => onChange(event.target.value);
|
|
32
32
|
|
|
33
33
|
return (
|
|
34
|
-
<div className={
|
|
34
|
+
<div className={cn(languageSwitcherRecipe({ variant, size }), className)}>
|
|
35
35
|
<select className={languageSwitcherSelect} value={currentLocale} onChange={handleSelectChange} disabled={disabled} aria-label="Switch language">
|
|
36
36
|
{placeholder && (
|
|
37
37
|
<option value="" disabled>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { clsx } from 'clsx';
|
|
2
1
|
import { ReactNode } from 'react';
|
|
3
2
|
|
|
4
3
|
import { logo } from './styles.css';
|
|
4
|
+
import { cn } from '../../utils/styleOverride';
|
|
5
5
|
|
|
6
6
|
export type LogoProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
7
7
|
children?: ReactNode;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
-
export const Logo = ({ children, className }: LogoProps) => <div className={
|
|
10
|
+
export const Logo = ({ children, className }: LogoProps) => <div className={cn(logo, className)}>{children}</div>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { clsx } from 'clsx';
|
|
2
|
-
|
|
3
1
|
import { main } from './styles.css';
|
|
2
|
+
import { cn } from '../../utils/styleOverride';
|
|
3
|
+
|
|
4
4
|
|
|
5
5
|
export type MainProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
6
6
|
children: React.ReactNode;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
export const Main = ({ className, children }: MainProps) => <main className={
|
|
9
|
+
export const Main = ({ className, children }: MainProps) => <main className={cn(main, className)}>{children}</main>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { clsx } from 'clsx';
|
|
4
3
|
import { ReactNode, useState } from 'react';
|
|
5
4
|
|
|
6
5
|
import { modal, modalContentCloseButton, modalContentRecipe, type ModalVariants } from './styles.css';
|
|
7
6
|
|
|
8
7
|
import { themeContract } from '../../theme/contract.css';
|
|
8
|
+
import { cn } from '../../utils/styleOverride';
|
|
9
9
|
import { Actions } from '../Actions';
|
|
10
10
|
import { Button } from '../Button';
|
|
11
11
|
import { Icon } from '../Icon';
|
|
@@ -39,7 +39,7 @@ export const Modal = ({ triggerLabel, className, children, align, withCloseButto
|
|
|
39
39
|
|
|
40
40
|
{isOpen && (
|
|
41
41
|
<div className={modal} role="dialog" aria-modal="true">
|
|
42
|
-
<div className={
|
|
42
|
+
<div className={cn(modalContentRecipe({ align }), className)}>
|
|
43
43
|
{withCloseButton && (
|
|
44
44
|
<button type="button" className={modalContentCloseButton} onClick={closeModal} aria-label="Close modal">
|
|
45
45
|
<Icon icon="close" color={themeContract.modal.closeButtonColor} />
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { clsx } from 'clsx';
|
|
2
|
-
|
|
3
1
|
import { navRecipe, NavVariants } from './styles.css';
|
|
2
|
+
import { cn } from '../../utils/styleOverride';
|
|
3
|
+
|
|
4
4
|
|
|
5
5
|
export type NavProps = React.HTMLAttributes<HTMLDivElement> &
|
|
6
6
|
NavVariants & {
|
|
7
7
|
children: React.ReactNode;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
-
export const Nav = ({ children, className, direction }: NavProps) => <div className={
|
|
10
|
+
export const Nav = ({ children, className, direction }: NavProps) => <div className={cn(navRecipe({ direction }), className)}>{children}</div>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { clsx } from 'clsx';
|
|
2
|
-
|
|
3
1
|
import { navLegalRecipe } from './styles.css';
|
|
2
|
+
import { cn } from '../../utils/styleOverride';
|
|
3
|
+
|
|
4
4
|
|
|
5
5
|
export type NavLegalProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
6
6
|
children: React.ReactNode;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
export const NavLegal = ({ className, children }: NavLegalProps) => <div className={
|
|
9
|
+
export const NavLegal = ({ className, children }: NavLegalProps) => <div className={cn(navLegalRecipe(), className)}>{children}</div>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { clsx } from 'clsx';
|
|
2
1
|
|
|
3
2
|
import { navSocialLink, navSocialRecipe } from './styles.css';
|
|
4
3
|
import { Social } from './types';
|
|
4
|
+
import { cn } from '../../utils/styleOverride';
|
|
5
5
|
import { Icon } from '../Icon';
|
|
6
6
|
import icons from '../Icon/path';
|
|
7
7
|
|
|
@@ -11,7 +11,7 @@ export type NavSocialProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
export const NavSocial = ({ size = 24, navSocial, className }: NavSocialProps) => (
|
|
14
|
-
<div className={
|
|
14
|
+
<div className={cn(navSocialRecipe(), className)}>
|
|
15
15
|
{navSocial?.map(({ name, link }, index) => {
|
|
16
16
|
const capitalized = name[0].toUpperCase() + name.slice(1);
|
|
17
17
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { clsx } from 'clsx';
|
|
2
1
|
import parse from 'html-react-parser';
|
|
3
2
|
|
|
4
3
|
import { paragraphRecipe, type ParagraphVariants } from './styles.css';
|
|
4
|
+
import { cn } from '../../utils/styleOverride';
|
|
5
5
|
|
|
6
6
|
export type ParagraphProps = React.HTMLAttributes<HTMLDivElement> &
|
|
7
7
|
ParagraphVariants & {
|
|
@@ -9,5 +9,5 @@ export type ParagraphProps = React.HTMLAttributes<HTMLDivElement> &
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
export const Paragraph = ({ align, className, children }: ParagraphProps) => (
|
|
12
|
-
<p className={
|
|
12
|
+
<p className={cn(paragraphRecipe({ align }), className)}>{parse(typeof children === 'string' ? children : '')}</p>
|
|
13
13
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { clsx } from 'clsx';
|
|
2
|
-
|
|
3
1
|
import { sectionContent, sectionRecipe, type SectionVariants } from './styles.css';
|
|
2
|
+
import { cn } from '../../utils/styleOverride';
|
|
3
|
+
|
|
4
4
|
|
|
5
5
|
export type SectionProps = React.HTMLAttributes<HTMLDivElement> &
|
|
6
6
|
SectionVariants & {
|
|
@@ -21,7 +21,7 @@ export const Section = ({
|
|
|
21
21
|
}: SectionProps) => (
|
|
22
22
|
<section
|
|
23
23
|
style={style}
|
|
24
|
-
className={
|
|
24
|
+
className={cn(sectionRecipe({ align, isDark, variant, isFullHeight, withPaddingTop, withPaddingBottom, isHeaderFixed }), className)}>
|
|
25
25
|
<div className={sectionContent}>{children}</div>
|
|
26
26
|
</section>
|
|
27
27
|
);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { clsx } from 'clsx';
|
|
4
3
|
import { useEffect, useRef, useState } from 'react';
|
|
5
4
|
|
|
6
5
|
import {
|
|
@@ -15,6 +14,7 @@ import {
|
|
|
15
14
|
type VideoVariants,
|
|
16
15
|
} from './styles.css';
|
|
17
16
|
import { themeContract } from '../../theme/contract.css';
|
|
17
|
+
import { cn } from '../../utils/styleOverride';
|
|
18
18
|
import { Icon } from '../Icon';
|
|
19
19
|
|
|
20
20
|
export type VideoProps = React.HTMLAttributes<HTMLDivElement> &
|
|
@@ -82,7 +82,7 @@ export const Video = ({
|
|
|
82
82
|
}, [isMuted]);
|
|
83
83
|
|
|
84
84
|
return (
|
|
85
|
-
<div className={
|
|
85
|
+
<div className={cn(videoRecipe({ size }), className)}>
|
|
86
86
|
<video ref={videoRef} className={videoElement} playsInline muted={isMuted} autoPlay={isAutoPlay} onEnded={handleVideoEnded}>
|
|
87
87
|
<source src={video} type="video/mp4" />
|
|
88
88
|
</video>
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { createLightTheme, createDarkTheme, type ThemeOverrides } from './theme/
|
|
|
6
6
|
// Styles utilities (sprinkles, media queries, etc.)
|
|
7
7
|
export { sprinkles, type Sprinkles } from './styles/sprinkles.css';
|
|
8
8
|
export { breakpoints, queries } from './styles/mediaqueries';
|
|
9
|
+
export { styleOverride, cn } from './utils/styleOverride';
|
|
9
10
|
|
|
10
11
|
// Components
|
|
11
12
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { style, StyleRule } from '@vanilla-extract/css';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a style override with higher specificity to ensure consumer styles
|
|
5
|
+
* always win over library component styles, regardless of CSS load order.
|
|
6
|
+
*
|
|
7
|
+
* Uses the [class] attribute selector to increase specificity from (0,1,0) to (0,2,0).
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* import { styleOverride } from '@latte-macchiat-io/latte-vanilla-components/utils/styleOverride';
|
|
12
|
+
*
|
|
13
|
+
* const customButton = styleOverride({
|
|
14
|
+
* textTransform: 'uppercase',
|
|
15
|
+
* letterSpacing: '0.1em',
|
|
16
|
+
* backgroundColor: 'red',
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* <Button className={customButton}>My Button</Button>
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function styleOverride(styleRule: StyleRule) {
|
|
23
|
+
return style({
|
|
24
|
+
selectors: {
|
|
25
|
+
'&[class]': styleRule,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Alternative utility that merges class names properly.
|
|
32
|
+
* This is a lightweight replacement for clsx with better type safety.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* import { cn } from '@latte-macchiat-io/latte-vanilla-components/utils/styleOverride';
|
|
37
|
+
*
|
|
38
|
+
* <Button className={cn(baseStyle, customStyle)} />
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function cn(...classes: (string | undefined | null | false)[]) {
|
|
42
|
+
return classes.filter(Boolean).join(' ');
|
|
43
|
+
}
|