@latte-macchiat-io/latte-vanilla-components 0.0.190 → 0.0.192

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.
Files changed (101) hide show
  1. package/README.md +16 -6
  2. package/package.json +4 -1
  3. package/src/components/Actions/index.tsx +20 -0
  4. package/src/components/Actions/styles.css.ts +54 -0
  5. package/src/components/Button/index.tsx +29 -0
  6. package/src/components/Button/stories.tsx +4 -22
  7. package/src/components/Button/styles.css.ts +131 -0
  8. package/src/components/Carousel/{Carousel.tsx → index.tsx} +18 -115
  9. package/src/components/Carousel/styles.css.ts +176 -0
  10. package/src/components/Columns/index.tsx +36 -0
  11. package/src/components/Columns/styles.css.ts +70 -0
  12. package/src/components/ConsentCookie/ConsentCookie.css.ts +1 -1
  13. package/src/components/ConsentCookie/ConsentCookie.tsx +3 -3
  14. package/src/components/Footer/index.tsx +21 -0
  15. package/src/components/Footer/styles.css.ts +33 -0
  16. package/src/components/Form/Form.css.ts +1 -1
  17. package/src/components/Form/Row/Row.css.ts +1 -1
  18. package/src/components/Form/TextField/Input/Input.css.ts +1 -1
  19. package/src/components/Form/TextField/Label/Label.css.ts +1 -1
  20. package/src/components/Form/TextField/TextField.css.ts +1 -1
  21. package/src/components/Form/TextField/Textarea/Textarea.css.ts +1 -1
  22. package/src/components/Header/index.tsx +53 -0
  23. package/src/components/Header/styles.css.ts +89 -0
  24. package/src/components/Heading/index.tsx +22 -0
  25. package/src/components/Heading/styles.css.ts +66 -0
  26. package/src/components/Heading/types.tsx +1 -0
  27. package/src/components/Icon/index.tsx +25 -0
  28. package/src/components/Icon/style.css.ts +11 -0
  29. package/src/components/KeyNumber/index.tsx +51 -0
  30. package/src/components/KeyNumber/styles.css.ts +76 -0
  31. package/src/components/LanguageSwitcher/index.tsx +80 -0
  32. package/src/components/LanguageSwitcher/{LanguageSwitcher.css.ts → styles.css.ts} +1 -1
  33. package/src/components/Logo/index.tsx +13 -0
  34. package/src/components/Logo/styles.css.ts +14 -0
  35. package/src/components/Main/index.tsx +17 -0
  36. package/src/components/Main/styles.css.ts +14 -0
  37. package/src/components/Modal/index.tsx +42 -0
  38. package/src/components/Modal/stories.tsx +14 -358
  39. package/src/components/Modal/styles.css.ts +90 -0
  40. package/src/components/Nav/index.tsx +22 -0
  41. package/src/components/Nav/styles.css.ts +30 -0
  42. package/src/components/NavLegal/index.tsx +17 -0
  43. package/src/components/NavLegal/styles.css.ts +20 -0
  44. package/src/components/NavSocial/index.tsx +32 -0
  45. package/src/components/NavSocial/styles.css.ts +33 -0
  46. package/src/components/Section/index.tsx +20 -0
  47. package/src/components/Section/stories.tsx +5 -57
  48. package/src/components/Section/styles.css.ts +106 -0
  49. package/src/components/ThemeTest/ThemeTest.css.ts +11 -0
  50. package/src/components/ThemeTest/ThemeTest.tsx +12 -0
  51. package/src/components/ThemeToggle/ThemeToggle.tsx +30 -0
  52. package/src/components/Video/index.tsx +117 -0
  53. package/src/components/Video/styles.css.ts +200 -0
  54. package/src/index.ts +29 -41
  55. package/src/styles/mediaqueries.ts +2 -0
  56. package/src/styles/sprinkles.css.ts +11 -8
  57. package/src/theme/baseThemeValues.ts +1235 -0
  58. package/src/theme/contract.css.ts +676 -0
  59. package/src/{themes → theme}/createTheme.ts +40 -1
  60. package/src/theme/default.css.ts +10 -0
  61. package/src/utils/combineResponsive.ts +9 -0
  62. package/src/utils/generateResponsiveMedia.ts +19 -0
  63. package/src/components/Actions/Actions.css.ts +0 -113
  64. package/src/components/Actions/Actions.tsx +0 -132
  65. package/src/components/Button/Button.css.ts +0 -119
  66. package/src/components/Button/Button.tsx +0 -132
  67. package/src/components/Carousel/Carousel.css.ts +0 -179
  68. package/src/components/Columns/Columns.css.ts +0 -185
  69. package/src/components/Columns/Columns.tsx +0 -142
  70. package/src/components/Footer/Footer.css.ts +0 -108
  71. package/src/components/Footer/Footer.tsx +0 -130
  72. package/src/components/Header/Header.css.ts +0 -111
  73. package/src/components/Header/Header.tsx +0 -158
  74. package/src/components/Icon/Icon.css.ts +0 -101
  75. package/src/components/Icon/Icon.tsx +0 -159
  76. package/src/components/KeyNumber/KeyNumber.css.ts +0 -158
  77. package/src/components/KeyNumber/KeyNumber.tsx +0 -166
  78. package/src/components/LanguageSwitcher/LanguageSwitcher.tsx +0 -168
  79. package/src/components/Logo/Logo.css.ts +0 -98
  80. package/src/components/Logo/Logo.tsx +0 -137
  81. package/src/components/Main/Main.css.ts +0 -62
  82. package/src/components/Main/Main.tsx +0 -130
  83. package/src/components/Modal/Modal.css.ts +0 -203
  84. package/src/components/Modal/Modal.tsx +0 -194
  85. package/src/components/Nav/Nav.css.ts +0 -123
  86. package/src/components/Nav/Nav.tsx +0 -130
  87. package/src/components/NavLegal/NavLegal.css.ts +0 -121
  88. package/src/components/NavLegal/NavLegal.tsx +0 -133
  89. package/src/components/NavSocial/NavSocial.css.ts +0 -121
  90. package/src/components/NavSocial/NavSocial.tsx +0 -169
  91. package/src/components/Section/Section.css.ts +0 -101
  92. package/src/components/Section/Section.tsx +0 -130
  93. package/src/components/Video/Video.css.ts +0 -210
  94. package/src/components/Video/Video.tsx +0 -243
  95. package/src/components/VideoFullWidth/VideoFullWidth.css.ts +0 -50
  96. package/src/components/VideoFullWidth/VideoFullWidth.tsx +0 -152
  97. package/src/components/VideoFullWidth/export.tsx +0 -2
  98. package/src/themes/baseThemeValues.ts +0 -160
  99. package/src/themes/contract.css.ts +0 -83
  100. package/src/types/withClassName.ts +0 -4
  101. /package/src/{utils → components/ConsentCookie}/cookie.ts +0 -0
@@ -1,373 +1,29 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
- import React, { useState } from 'react';
3
- import { Modal } from './Modal';
4
- import { Button } from '../Button/Button';
5
- import { Section } from '../Section/Section';
2
+ import React from 'react';
6
3
 
4
+ import { Modal } from '../../index';
5
+
6
+ // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
7
7
  const meta: Meta<typeof Modal> = {
8
- title: 'Interactive Components/Modal',
8
+ title: 'Latte Components / 1. Global / Modal',
9
9
  component: Modal,
10
10
  parameters: {
11
- layout: 'fullscreen',
12
- docs: {
13
- description: {
14
- component: `
15
- The Modal component provides an accessible, animated modal dialog with various customization options.
16
-
17
- ## Features
18
- - Accessible (ARIA compliant, focus management)
19
- - Animated entrance/exit
20
- - Multiple sizes (small, medium, large, fullscreen)
21
- - Centered or top-aligned positioning
22
- - Backdrop click to close (optional)
23
- - Escape key to close (optional)
24
- - Close button (optional)
25
- - Body scroll lock when open
26
- - TypeScript support
27
-
28
- ## Accessibility
29
- - Traps focus within the modal
30
- - Restores focus to trigger element on close
31
- - Supports keyboard navigation
32
- - ARIA attributes for screen readers
33
- `,
34
- },
35
- },
11
+ // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
12
+ layout: 'centered',
36
13
  },
14
+ // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
37
15
  tags: ['autodocs'],
38
- argTypes: {
39
- isOpen: {
40
- control: 'boolean',
41
- description: 'Controls whether the modal is visible',
42
- },
43
- size: {
44
- control: 'select',
45
- options: ['small', 'medium', 'large', 'fullscreen'],
46
- description: 'Size of the modal dialog',
47
- },
48
- centered: {
49
- control: 'boolean',
50
- description: 'Whether to center the modal vertically',
51
- },
52
- showCloseButton: {
53
- control: 'boolean',
54
- description: 'Whether to show the close button',
55
- },
56
- closeOnBackdropClick: {
57
- control: 'boolean',
58
- description: 'Whether clicking the backdrop closes the modal',
59
- },
60
- closeOnEscape: {
61
- control: 'boolean',
62
- description: 'Whether pressing Escape closes the modal',
63
- },
64
- },
16
+ // More on argTypes: https://storybook.js.org/docs/api/argtypes
17
+ argTypes: {},
18
+ // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
65
19
  };
66
20
 
67
21
  export default meta;
68
- type Story = StoryObj<typeof Modal>;
69
-
70
- // Interactive Modal Example
71
- const InteractiveModal = ({ size = 'medium', centered = true, ...args }: any) => {
72
- const [isOpen, setIsOpen] = useState(false);
73
-
74
- return (
75
- <Section>
76
- <Button variant="primary" onClick={() => setIsOpen(true)}>
77
- Open Modal
78
- </Button>
79
- <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} size={size} centered={centered} {...args}>
80
- <div style={{ padding: '2rem' }}>
81
- <h2>Modal Title</h2>
82
- <p>This is the modal content. You can put any React components here.</p>
83
- <div style={{ marginTop: '2rem', display: 'flex', gap: '1rem' }}>
84
- <Button variant="primary" onClick={() => setIsOpen(false)}>
85
- Confirm
86
- </Button>
87
- <Button variant="secondary" onClick={() => setIsOpen(false)}>
88
- Cancel
89
- </Button>
90
- </div>
91
- </div>
92
- </Modal>
93
- </Section>
94
- );
95
- };
22
+ type Story = StoryObj<typeof meta>;
96
23
 
24
+ // More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
97
25
  export const Default: Story = {
98
- render: (args) => <InteractiveModal {...args} />,
99
26
  args: {
100
- size: 'md',
101
- centered: true,
102
- showCloseButton: true,
103
- closeOnBackdropClick: true,
104
- closeOnEscape: true,
105
- },
106
- };
107
-
108
- export const Small: Story = {
109
- render: (args) => <InteractiveModal {...args} size="small" />,
110
- parameters: {
111
- docs: {
112
- description: {
113
- story: 'Small modal size, perfect for confirmations and quick actions.',
114
- },
115
- },
116
- },
117
- };
118
-
119
- export const Medium: Story = {
120
- render: (args) => <InteractiveModal {...args} size="medium" />,
121
- parameters: {
122
- docs: {
123
- description: {
124
- story: 'Medium modal size, the default and most commonly used size.',
125
- },
126
- },
127
- },
128
- };
129
-
130
- export const Large: Story = {
131
- render: (args) => <InteractiveModal {...args} size="large" />,
132
- parameters: {
133
- docs: {
134
- description: {
135
- story: 'Large modal size, good for forms and detailed content.',
136
- },
137
- },
138
- },
139
- };
140
-
141
- export const Fullscreen: Story = {
142
- render: (args) => <InteractiveModal {...args} size="fullscreen" />,
143
- parameters: {
144
- docs: {
145
- description: {
146
- story: 'Fullscreen modal that takes up the entire viewport.',
147
- },
148
- },
149
- },
150
- };
151
-
152
- export const TopAligned: Story = {
153
- render: (args) => <InteractiveModal {...args} centered={false} />,
154
- parameters: {
155
- docs: {
156
- description: {
157
- story: 'Modal aligned to the top of the viewport instead of centered.',
158
- },
159
- },
160
- },
161
- };
162
-
163
- export const NoCloseButton: Story = {
164
- render: (args) => <InteractiveModal {...args} showCloseButton={false} />,
165
- parameters: {
166
- docs: {
167
- description: {
168
- story: 'Modal without the close button - must be closed programmatically.',
169
- },
170
- },
171
- },
172
- };
173
-
174
- export const NoBackdropClose: Story = {
175
- render: (args) => <InteractiveModal {...args} closeOnBackdropClick={false} />,
176
- parameters: {
177
- docs: {
178
- description: {
179
- story: 'Modal that cannot be closed by clicking the backdrop.',
180
- },
181
- },
182
- },
183
- };
184
-
185
- export const ConfirmationDialog: Story = {
186
- render: () => {
187
- const [isOpen, setIsOpen] = useState(false);
188
-
189
- return (
190
- <Section>
191
- <Button variant="danger" onClick={() => setIsOpen(true)}>
192
- Delete Item
193
- </Button>
194
- <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} size="sm" centered={true}>
195
- <div style={{ padding: '2rem', textAlign: 'center' }}>
196
- <h3 style={{ color: 'var(--latte-colors-error)', marginBottom: '1rem' }}>Delete Confirmation</h3>
197
- <p style={{ marginBottom: '2rem' }}>Are you sure you want to delete this item? This action cannot be undone.</p>
198
- <div style={{ display: 'flex', gap: '1rem', justifyContent: 'center' }}>
199
- <Button variant="danger" onClick={() => setIsOpen(false)}>
200
- Delete
201
- </Button>
202
- <Button variant="secondary" onClick={() => setIsOpen(false)}>
203
- Cancel
204
- </Button>
205
- </div>
206
- </div>
207
- </Modal>
208
- </Section>
209
- );
210
- },
211
- parameters: {
212
- docs: {
213
- description: {
214
- story: 'A real-world example of a confirmation dialog for destructive actions.',
215
- },
216
- },
217
- },
218
- };
219
-
220
- export const FormModal: Story = {
221
- render: () => {
222
- const [isOpen, setIsOpen] = useState(false);
223
-
224
- return (
225
- <Section>
226
- <Button variant="primary" onClick={() => setIsOpen(true)}>
227
- Add New User
228
- </Button>
229
- <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} size="md" centered={true}>
230
- <div style={{ padding: '2rem' }}>
231
- <h2 style={{ marginBottom: '2rem' }}>Add New User</h2>
232
- <form style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
233
- <div>
234
- <label style={{ display: 'block', marginBottom: '0.5rem', fontWeight: '500' }}>Name</label>
235
- <input
236
- type="text"
237
- style={{
238
- width: '100%',
239
- padding: '0.75rem',
240
- border: '1px solid var(--latte-colors-border)',
241
- borderRadius: 'var(--latte-radii-md)',
242
- fontSize: 'var(--latte-fontSizes-md)',
243
- }}
244
- />
245
- </div>
246
- <div>
247
- <label style={{ display: 'block', marginBottom: '0.5rem', fontWeight: '500' }}>Email</label>
248
- <input
249
- type="email"
250
- style={{
251
- width: '100%',
252
- padding: '0.75rem',
253
- border: '1px solid var(--latte-colors-border)',
254
- borderRadius: 'var(--latte-radii-md)',
255
- fontSize: 'var(--latte-fontSizes-md)',
256
- }}
257
- />
258
- </div>
259
- <div>
260
- <label style={{ display: 'block', marginBottom: '0.5rem', fontWeight: '500' }}>Role</label>
261
- <select
262
- style={{
263
- width: '100%',
264
- padding: '0.75rem',
265
- border: '1px solid var(--latte-colors-border)',
266
- borderRadius: 'var(--latte-radii-md)',
267
- fontSize: 'var(--latte-fontSizes-md)',
268
- }}>
269
- <option>User</option>
270
- <option>Admin</option>
271
- <option>Moderator</option>
272
- </select>
273
- </div>
274
- <div style={{ marginTop: '2rem', display: 'flex', gap: '1rem', justifyContent: 'flex-end' }}>
275
- <Button variant="secondary" onClick={() => setIsOpen(false)}>
276
- Cancel
277
- </Button>
278
- <Button variant="primary" onClick={() => setIsOpen(false)}>
279
- Add User
280
- </Button>
281
- </div>
282
- </form>
283
- </div>
284
- </Modal>
285
- </Section>
286
- );
287
- },
288
- parameters: {
289
- docs: {
290
- description: {
291
- story: 'A practical example of using a modal for form input.',
292
- },
293
- },
294
- },
295
- };
296
-
297
- export const ImageGallery: Story = {
298
- render: () => {
299
- const [isOpen, setIsOpen] = useState(false);
300
- const [selectedImage, setSelectedImage] = useState(0);
301
-
302
- const images = [
303
- 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800&h=600&fit=crop',
304
- 'https://images.unsplash.com/photo-1519904981063-b0cf448d479e?w=800&h=600&fit=crop',
305
- 'https://images.unsplash.com/photo-1454391304352-2bf4678b1a7a?w=800&h=600&fit=crop',
306
- ];
307
-
308
- const openImage = (index: number) => {
309
- setSelectedImage(index);
310
- setIsOpen(true);
311
- };
312
-
313
- return (
314
- <Section>
315
- <h3>Click an image to view in modal:</h3>
316
- <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '1rem', marginTop: '1rem' }}>
317
- {images.map((src, index) => (
318
- <img
319
- key={index}
320
- src={src}
321
- alt={`Gallery ${index + 1}`}
322
- style={{
323
- width: '100%',
324
- height: '150px',
325
- objectFit: 'cover',
326
- borderRadius: 'var(--latte-radii-md)',
327
- cursor: 'pointer',
328
- }}
329
- onClick={() => openImage(index)}
330
- />
331
- ))}
332
- </div>
333
- <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} size="lg" centered={true}>
334
- <div style={{ padding: '1rem' }}>
335
- <img
336
- src={images[selectedImage]}
337
- alt={`Gallery ${selectedImage + 1}`}
338
- style={{
339
- width: '100%',
340
- height: 'auto',
341
- borderRadius: 'var(--latte-radii-md)',
342
- }}
343
- />
344
- <div
345
- style={{
346
- marginTop: '1rem',
347
- display: 'flex',
348
- justifyContent: 'space-between',
349
- alignItems: 'center',
350
- }}>
351
- <Button variant="ghost" onClick={() => setSelectedImage((prev) => (prev > 0 ? prev - 1 : images.length - 1))}>
352
- ← Previous
353
- </Button>
354
- <span>
355
- Image {selectedImage + 1} of {images.length}
356
- </span>
357
- <Button variant="ghost" onClick={() => setSelectedImage((prev) => (prev < images.length - 1 ? prev + 1 : 0))}>
358
- Next →
359
- </Button>
360
- </div>
361
- </div>
362
- </Modal>
363
- </Section>
364
- );
365
- },
366
- parameters: {
367
- docs: {
368
- description: {
369
- story: 'An example of using modals for image galleries with navigation.',
370
- },
371
- },
27
+ children: <div>Hellooooo</div>,
372
28
  },
373
29
  };
@@ -0,0 +1,90 @@
1
+ import { style } from '@vanilla-extract/css';
2
+ import { recipe, type RecipeVariants } from '@vanilla-extract/recipes';
3
+
4
+ import { themeContract } from '../../theme/contract.css';
5
+ import { generateResponsiveMedia } from '../../utils/generateResponsiveMedia';
6
+
7
+ export const modal = style({
8
+ zIndex: 100,
9
+ position: 'fixed',
10
+
11
+ selectors: {
12
+ '&:before': {
13
+ top: 0,
14
+ left: 0,
15
+ right: 0,
16
+ bottom: 0,
17
+ content: "''",
18
+ position: 'fixed',
19
+ backdropFilter: `blur(${themeContract.modal.overlayBlur})`,
20
+ backgroundColor: themeContract.modal.overlayBackgroundColor,
21
+ },
22
+ },
23
+ });
24
+
25
+ export const modalContentRecipe = recipe({
26
+ base: [
27
+ {
28
+ top: '50%',
29
+ left: '50%',
30
+ display: 'flex',
31
+ position: 'fixed',
32
+ flexDirection: 'column',
33
+
34
+ color: themeContract.colors.text,
35
+ transform: 'translate(-50%, -50%)',
36
+ borderRadius: themeContract.modal.borderRadius,
37
+ backgroundColor: themeContract.modal.backgroundColor,
38
+
39
+ '@media': {
40
+ ...generateResponsiveMedia({
41
+ gap: themeContract.modal.gap,
42
+ width: themeContract.modal.width,
43
+ maxWidth: {
44
+ mobile: `calc(100% - ${themeContract.global.paddingLeft.mobile} - ${themeContract.global.paddingRight.mobile})`,
45
+ sm: `calc(100% - ${themeContract.global.paddingLeft.sm} - ${themeContract.global.paddingRight.sm})`,
46
+ md: `calc(100% - ${themeContract.global.paddingLeft.md} - ${themeContract.global.paddingRight.md})`,
47
+ lg: `calc(100% - ${themeContract.global.paddingLeft.lg} - ${themeContract.global.paddingRight.lg})`,
48
+ xl: `calc(100% - ${themeContract.global.paddingLeft.xl} - ${themeContract.global.paddingRight.xl})`,
49
+ '2xl': `calc(100% - ${themeContract.global.paddingLeft['2xl']} - ${themeContract.global.paddingRight['2xl']})`,
50
+ },
51
+ paddingTop: themeContract.modal.paddingTop,
52
+ paddingLeft: themeContract.global.paddingLeft,
53
+ paddingRight: themeContract.global.paddingRight,
54
+ paddingBottom: themeContract.modal.paddingBottom,
55
+ }),
56
+ },
57
+ },
58
+ ],
59
+
60
+ variants: {
61
+ align: {
62
+ left: { alignItems: 'flex-start' },
63
+ center: { alignItems: 'center' },
64
+ right: { alignItems: 'flex-end' },
65
+ },
66
+ },
67
+ defaultVariants: {
68
+ align: 'center',
69
+ },
70
+ });
71
+
72
+ export const modalContentCloseButton = style([
73
+ {
74
+ border: 0,
75
+ lineHeight: '1em',
76
+ cursor: 'pointer',
77
+ position: 'absolute',
78
+
79
+ maxWidth: themeContract.maxWidth,
80
+
81
+ '@media': {
82
+ ...generateResponsiveMedia({
83
+ top: themeContract.modal.closeButtonTopPosition,
84
+ right: themeContract.modal.closeButtonRightPosition,
85
+ }),
86
+ },
87
+ },
88
+ ]);
89
+
90
+ export type ModalVariants = RecipeVariants<typeof modalContentRecipe>;
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+
3
+ import { clsx } from 'clsx';
4
+ import { forwardRef } from 'react';
5
+
6
+ import { navRecipe } from './styles.css';
7
+
8
+ export type NavProps = React.HTMLAttributes<HTMLDivElement> & {
9
+ css?: string;
10
+ direction?: 'column' | 'row';
11
+ children: React.ReactNode;
12
+ };
13
+
14
+ export const Nav = forwardRef<HTMLDivElement, NavProps>(({ children, css, className, direction = 'column' }, ref) => {
15
+ return (
16
+ <div ref={ref} className={clsx(navRecipe({ direction }), css, className)}>
17
+ {children}
18
+ </div>
19
+ );
20
+ });
21
+
22
+ Nav.displayName = 'Nav';
@@ -0,0 +1,30 @@
1
+ import { recipe } from '@vanilla-extract/recipes';
2
+
3
+ import { themeContract } from '../../theme/contract.css';
4
+ import { generateResponsiveMedia } from '../../utils/generateResponsiveMedia';
5
+
6
+ export const navRecipe = recipe({
7
+ base: [
8
+ {
9
+ display: 'flex',
10
+ flexDirection: 'column',
11
+
12
+ '@media': {
13
+ ...generateResponsiveMedia({
14
+ gap: themeContract.nav.gap,
15
+ }),
16
+ },
17
+ },
18
+ ],
19
+
20
+ variants: {
21
+ direction: {
22
+ row: { flexDirection: 'row' },
23
+ column: { flexDirection: 'column' },
24
+ },
25
+ },
26
+
27
+ defaultVariants: {
28
+ direction: 'column',
29
+ },
30
+ });
@@ -0,0 +1,17 @@
1
+ import { clsx } from 'clsx';
2
+ import { forwardRef } from 'react';
3
+
4
+ import { navLegalRecipe } from './styles.css';
5
+
6
+ export type NavLegalProps = React.HTMLAttributes<HTMLDivElement> & {
7
+ css?: string;
8
+ children: React.ReactNode;
9
+ };
10
+
11
+ export const NavLegal = forwardRef<HTMLDivElement, NavLegalProps>(({ css, className, children }, ref) => (
12
+ <div ref={ref} className={clsx(navLegalRecipe(), css, className)}>
13
+ {children}
14
+ </div>
15
+ ));
16
+
17
+ NavLegal.displayName = 'NavLegal';
@@ -0,0 +1,20 @@
1
+ import { style } from '@vanilla-extract/css';
2
+ import { recipe } from '@vanilla-extract/recipes';
3
+
4
+ import { themeContract } from '../../theme/contract.css';
5
+ import { generateResponsiveMedia } from '../../utils/generateResponsiveMedia';
6
+
7
+ export const navLegalRecipe = recipe({
8
+ base: [
9
+ style({
10
+ display: 'flex',
11
+ flexDirection: 'row',
12
+
13
+ '@media': {
14
+ ...generateResponsiveMedia({
15
+ gap: themeContract.navLegal.gap,
16
+ }),
17
+ },
18
+ }),
19
+ ],
20
+ });
@@ -0,0 +1,32 @@
1
+ import { clsx } from 'clsx';
2
+ import { forwardRef } from 'react';
3
+
4
+ import { navSocialLink, navSocialRecipe } from './styles.css';
5
+ import { Social } from './types';
6
+ import { Icon } from '../Icon';
7
+ import icons from '../Icon/path';
8
+
9
+ export type NavSocialProps = React.HTMLAttributes<HTMLDivElement> & {
10
+ navSocial: { name: Social; link: string }[];
11
+ css?: string;
12
+ };
13
+
14
+ export const NavSocial = forwardRef<HTMLDivElement, NavSocialProps>(({ navSocial, css, className }, ref) => {
15
+ return (
16
+ <div ref={ref} className={clsx(navSocialRecipe(), css, className)}>
17
+ {navSocial?.map(({ name, link }, index) => {
18
+ const capitalized = name[0].toUpperCase() + name.slice(1);
19
+
20
+ return (
21
+ <div key={`navSocialItem-${index}`} className={navSocialLink}>
22
+ <a target="_blank" rel="noopener noreferrer" aria-label={name} href={link}>
23
+ <Icon icon={`social${capitalized}` as keyof typeof icons} />
24
+ </a>
25
+ </div>
26
+ );
27
+ })}
28
+ </div>
29
+ );
30
+ });
31
+
32
+ NavSocial.displayName = 'NavSocial';
@@ -0,0 +1,33 @@
1
+ import { globalStyle, style } from '@vanilla-extract/css';
2
+ import { recipe } from '@vanilla-extract/recipes';
3
+
4
+ import { themeContract } from '../../theme/contract.css';
5
+ import { generateResponsiveMedia } from '../../utils/generateResponsiveMedia';
6
+
7
+ export const navSocialRecipe = recipe({
8
+ base: [
9
+ {
10
+ display: 'flex',
11
+ flexDirection: 'row',
12
+
13
+ '@media': {
14
+ ...generateResponsiveMedia({
15
+ gap: themeContract.navSocial.gap,
16
+ }),
17
+ },
18
+ },
19
+ ],
20
+ });
21
+
22
+ export const navSocialLink = style({
23
+ padding: 10,
24
+ });
25
+
26
+ globalStyle(`${navSocialLink} svg path`, {
27
+ transition: themeContract.navSocial.transition,
28
+ fill: themeContract.navSocial.colors.defaultIcon,
29
+ });
30
+
31
+ globalStyle(`${navSocialLink}:hover svg path`, {
32
+ fill: themeContract.navSocial.colors.defaultIconHover,
33
+ });
@@ -0,0 +1,20 @@
1
+ import { clsx } from 'clsx';
2
+ import { forwardRef } from 'react';
3
+
4
+ import { sectionContent, sectionRecipe, type SectionVariants } from './styles.css';
5
+
6
+ export type SectionProps = React.HTMLAttributes<HTMLDivElement> &
7
+ SectionVariants & {
8
+ css?: string;
9
+ children: React.ReactNode;
10
+ };
11
+
12
+ export const Section = forwardRef<HTMLElement, SectionProps>(
13
+ ({ align, isDark, isFullHeight, withPaddingTop, withPaddingBottom, css, className, children }, ref) => (
14
+ <section ref={ref} className={clsx(sectionRecipe({ align, isDark, isFullHeight, withPaddingTop, withPaddingBottom }), css, className)}>
15
+ <div className={sectionContent}>{children}</div>
16
+ </section>
17
+ )
18
+ );
19
+
20
+ Section.displayName = 'Section';