@majordigital/create-acorn 1.0.4 → 1.0.6

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 (57) hide show
  1. package/README.md +35 -70
  2. package/bin/create-acorn.mjs +28 -2
  3. package/package.json +2 -1
  4. package/template/next.config.js +48 -0
  5. package/template/public/favicon/favicon.ico +0 -0
  6. package/template/src/app/layout.tsx +60 -0
  7. package/template/src/app/not-found.tsx +7 -0
  8. package/template/src/app/page.tsx +7 -0
  9. package/template/src/app/robots.ts +32 -0
  10. package/template/src/app/sitemap.ts +22 -0
  11. package/template/src/icons/logo.svg +3 -0
  12. package/template/src/lib/buildCache.ts +29 -0
  13. package/template/src/lib/config.ts +7 -0
  14. package/template/src/lib/constants.ts +0 -0
  15. package/template/src/lib/fonts.ts +15 -0
  16. package/template/src/lib/getMetadata.ts +124 -0
  17. package/template/src/lib/utils.ts +12 -0
  18. package/template/src/styles/globals.css +23 -0
  19. package/template/src/types/components.ts +25 -0
  20. package/template/src/types/custom.d.ts +3 -0
  21. package/template/src/ui/ConditionalWrapper.tsx +13 -0
  22. package/template/src/ui/components/Accordion.tsx +73 -0
  23. package/template/src/ui/components/AnnouncementBar.tsx +36 -0
  24. package/template/src/ui/components/Breadcrumbs.tsx +60 -0
  25. package/template/src/ui/components/ButtonGroup.tsx +42 -0
  26. package/template/src/ui/components/CallToAction.tsx +21 -0
  27. package/template/src/ui/components/Card.tsx +21 -0
  28. package/template/src/ui/components/FeaturedContent.tsx +21 -0
  29. package/template/src/ui/components/FormContact.tsx +190 -0
  30. package/template/src/ui/components/Nav.tsx +39 -0
  31. package/template/src/ui/components/NavCollapsed.tsx +91 -0
  32. package/template/src/ui/components/Pagination.tsx +96 -0
  33. package/template/src/ui/components/Quote.tsx +21 -0
  34. package/template/src/ui/elements/Button.tsx +97 -0
  35. package/template/src/ui/elements/ButtonWrapper.tsx +42 -0
  36. package/template/src/ui/elements/Chip.tsx +27 -0
  37. package/template/src/ui/elements/Tooltip.tsx +71 -0
  38. package/template/src/ui/elements/form/Checkbox.tsx +24 -0
  39. package/template/src/ui/elements/form/Form.tsx +134 -0
  40. package/template/src/ui/elements/form/FormLabel.tsx +15 -0
  41. package/template/src/ui/elements/form/FormMessage.tsx +34 -0
  42. package/template/src/ui/elements/form/Input.tsx +24 -0
  43. package/template/src/ui/elements/form/Textarea.tsx +24 -0
  44. package/template/src/ui/elements/navigation/NavPopover.tsx +84 -0
  45. package/template/src/ui/elements/navigation/NavPrimaryLink.tsx +27 -0
  46. package/template/src/ui/elements/navigation/NavSecondaryLink.tsx +28 -0
  47. package/template/src/ui/elements/typography/Blockquote.tsx +30 -0
  48. package/template/src/ui/elements/typography/H.tsx +92 -0
  49. package/template/src/ui/elements/typography/List.tsx +64 -0
  50. package/template/src/ui/elements/typography/P.tsx +88 -0
  51. package/template/src/ui/elements/typography/TypoWrapper.tsx +15 -0
  52. package/template/src/ui/layout/Container.tsx +27 -0
  53. package/template/src/ui/layout/PageSection.tsx +39 -0
  54. package/template/src/ui/sections/Footer.tsx +11 -0
  55. package/template/src/ui/sections/Header.tsx +21 -0
  56. package/template/tailwind.config.js +33 -0
  57. package/template/tsconfig.json +69 -0
package/README.md CHANGED
@@ -1,92 +1,57 @@
1
- # Acorn
1
+ # @majordigital/create-acorn
2
2
 
3
- ## Introduction 📖
3
+ Interactive CLI to scaffold a Next.js 15 project with a headless CMS and Acorn components.
4
4
 
5
- This is MAJOR's starter boilerplate for NextJs projects. You can either use it without a headless CMS, connect it to a headless CMS of your choice, or use the blueprints we have for our current evolving selection of headless CMS's which includes:
6
-
7
- - Primsic
8
- - Storyblok
9
-
10
- ## Running This Project 🚀
11
-
12
- This website is built using the [NextJS](https://nextjs.org/) framework, utilising [TypeScript](https://www.typescriptlang.org/) to strongly check types/props and provide helpful intelisense, and the utility-first CSS framework [Tailwind](https://tailwindcss.com/) as the styling library.
13
-
14
- To run this project, following the following instructions:
15
-
16
- 1. **Install dependencies**
17
- Navigate to the root directory.
18
-
19
- ```bash
20
- npm i
21
- ```
22
-
23
- 2. **Run the website**
24
- In the root directory.
25
-
26
- ```bash
27
- npm run dev
28
- ```
29
-
30
- ## Create Acorn CLI 🧰
31
-
32
- First iteration of an interactive CLI to scaffold a Headless CMS skeleton with Next.js 15 and Acorn components.
33
-
34
- - Default framework: Next.js 15
35
- - UI base: Acorn components (this repo)
36
- - CMS options: Prismic, Storyblok, Dato
37
-
38
- Recommended usage (once published to npm):
5
+ ## Quick Start
39
6
 
40
7
  ```bash
41
8
  npx @majordigital/create-acorn@latest
42
9
  ```
43
10
 
44
- Local (from this repo):
11
+ The CLI will walk you through selecting a CMS (Prismic, Storyblok, or Dato) and scaffold a Next.js 15 project with TypeScript, Tailwind, and Biome.
45
12
 
46
- ```bash
47
- npm run create
48
- # or
49
- npm run acorn
50
- ```
51
-
52
- ### Prismic setup
13
+ ## What You Get
53
14
 
54
- If you select Prismic, the CLI follows the official Next.js guide and runs Slice Machine init for you:
15
+ The CLI scaffolds a complete project with:
55
16
 
56
- ```bash
57
- npx @slicemachine/init@latest --repository <your-repository-name>
58
- ```
17
+ - **Next.js 15** with App Router, TypeScript, and Tailwind
18
+ - **Acorn component library** — UI elements, layout components, sections, forms, typography, and navigation
19
+ - **Biome** for linting and formatting
20
+ - **Your chosen CMS** pre-configured
59
21
 
60
- You can also pass the repository name non-interactively:
22
+ ### Included Acorn Boilerplate
61
23
 
62
- ```bash
63
- npm run acorn -- --cms prismic --repo my-repo
64
- # or
65
- npx @majordigital/create-acorn@latest --cms prismic --repo my-repo
24
+ ```
25
+ src/
26
+ ├── app/ # Next.js app routes (layout, page, not-found, robots, sitemap)
27
+ ├── icons/ # SVG icons
28
+ ├── lib/ # Utilities, fonts, config, metadata helpers
29
+ ├── styles/ # Global CSS
30
+ ├── types/ # TypeScript type definitions
31
+ └── ui/
32
+ ├── components/ # Nav, Card, Accordion, CTA, Quote, Form, etc.
33
+ ├── elements/ # Button, Chip, Tooltip, typography, form inputs
34
+ ├── layout/ # Container, PageSection
35
+ └── sections/ # Header, Footer
66
36
  ```
67
37
 
68
- Repository name rules: lowercase letters, numbers, and hyphens only.
69
-
70
- Or pass a non-interactive flag:
38
+ ## Non-Interactive Usage
71
39
 
72
40
  ```bash
73
- node bin/create-acorn.mjs --cms prismic
74
- node bin/create-acorn.mjs --cms storyblok
75
- node bin/create-acorn.mjs --cms dato
41
+ npx @majordigital/create-acorn@latest --cms prismic --repo my-repo
42
+ npx @majordigital/create-acorn@latest --cms storyblok
43
+ npx @majordigital/create-acorn@latest --cms dato
76
44
  ```
77
45
 
78
- Notes:
79
- - This first iteration collects your CMS choice only and prints the selection. In the next iteration, the CLI will scaffold the appropriate folder structure and config for your chosen CMS.
80
- - To expose this CLI as global commands (`create-acorn` and `major-acorn`) via `npm create`/`npx`, remove `"private": true` and publish to npm.
81
- - While it’s technically possible to trigger prompts on `npm install major-acorn` via a `postinstall` script, it’s discouraged as it surprises installs and breaks CI. The recommended flow is `npm create acorn` or `npx major-acorn` to start a new project.
46
+ ## Prismic Setup
82
47
 
83
- ## Contacts 🧑‍💻
48
+ If you select Prismic, the CLI runs Slice Machine init and connects to your new or existing repository.
84
49
 
85
- The primary contact for this project is [Davs Howard](mailto:davs@majordigital.com).
50
+ Repository name rules: lowercase letters, numbers, and hyphens only.
86
51
 
87
- ### Roadmap
52
+ After setup:
88
53
 
89
- - [Commitizen](https://github.com/commitizen/cz-cli)
90
- - Integration and E2E testing
91
- - Storybook
92
- - GitHub workflows
54
+ 1. Start Slice Machine: `npm run slicemachine`
55
+ 2. Create your custom types at http://localhost:9999
56
+ 3. Push your custom types to Prismic
57
+ 4. Start your dev server: `npm run dev`
@@ -3,8 +3,9 @@
3
3
  import readline from 'node:readline';
4
4
  import { argv, exit } from 'node:process';
5
5
  import { spawn } from 'node:child_process';
6
- import { basename, join } from 'node:path';
7
- import { readFileSync, writeFileSync } from 'node:fs';
6
+ import { basename, join, dirname } from 'node:path';
7
+ import { readFileSync, writeFileSync, cpSync, rmSync } from 'node:fs';
8
+ import { fileURLToPath } from 'node:url';
8
9
 
9
10
  const CMS_CHOICES = [
10
11
  { key: 'prismic', label: 'Prismic' },
@@ -96,6 +97,31 @@ async function scaffoldNextApp() {
96
97
  console.log('Next.js project scaffolded successfully.');
97
98
  console.log('');
98
99
 
100
+ // Remove postcss config generated by create-next-app (not needed with Tailwind v4)
101
+ try { rmSync(join(process.cwd(), 'postcss.config.mjs')); } catch {}
102
+
103
+ // Copy Acorn template files (src/, public/, config files) over the Next.js scaffold
104
+ console.log('Copying Acorn boilerplate...');
105
+ const __dirname = dirname(fileURLToPath(import.meta.url));
106
+ const templateDir = join(__dirname, '..', 'template');
107
+ cpSync(templateDir, process.cwd(), { recursive: true, force: true });
108
+ console.log('Acorn components and config copied successfully.');
109
+ console.log('');
110
+
111
+ // Install Acorn dependencies
112
+ console.log('Installing Acorn dependencies...');
113
+ await runCommand('npm', ['install', '--legacy-peer-deps',
114
+ '@headlessui/react', '@headlessui/tailwindcss', '@heroicons/react',
115
+ '@hookform/error-message', '@hookform/resolvers',
116
+ '@next/bundle-analyzer', 'clsx', 'next-seo',
117
+ 'react-accessible-accordion', 'react-hook-form', 'zod'
118
+ ]);
119
+ await runCommand('npm', ['install', '--save-dev', '--legacy-peer-deps',
120
+ '@svgr/webpack', '@types/lodash.get'
121
+ ]);
122
+ console.log('Acorn dependencies installed.');
123
+ console.log('');
124
+
99
125
  console.log('Installing Biome for linting...');
100
126
  await runCommand('npm', ['install', '--save-dev', '--save-exact', '@biomejs/biome']);
101
127
  await runCommand('npx', ['@biomejs/biome', 'init']);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@majordigital/create-acorn",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Interactive scaffold for Acorn with Storyblok/Prismic/DatoCMS, TypeScript, and Tailwind.",
5
5
  "bin": {
6
6
  "create-acorn": "bin/create-acorn.mjs",
@@ -19,6 +19,7 @@
19
19
  },
20
20
  "files": [
21
21
  "bin",
22
+ "template",
22
23
  "README.md"
23
24
  ],
24
25
  "engines": {
@@ -0,0 +1,48 @@
1
+ /** @type {import('next').NextConfig} */
2
+
3
+ const baseConfig = {
4
+ poweredByHeader: false,
5
+ reactStrictMode: true,
6
+ trailingSlash: false,
7
+
8
+ turbopack: {
9
+ rules: {
10
+ '*.svg': {
11
+ loaders: ['@svgr/webpack'],
12
+ as: '*.js',
13
+ },
14
+ },
15
+ },
16
+
17
+ webpack(config) {
18
+ config.module.rules.push({
19
+ test: /\.svg$/,
20
+ use: ['@svgr/webpack'],
21
+ });
22
+
23
+ return config;
24
+ },
25
+
26
+ images: {
27
+ remotePatterns: [
28
+ {
29
+ protocol: 'https',
30
+ hostname: 'images.prismic.io',
31
+ port: '',
32
+ pathname: '/**',
33
+ },
34
+ {
35
+ protocol: 'https',
36
+ hostname: 'io.prismic.preview',
37
+ port: '',
38
+ pathname: '/**',
39
+ },
40
+ ],
41
+ },
42
+ };
43
+
44
+ const withBundleAnalyzer = require('@next/bundle-analyzer')({
45
+ enabled: process.env.ANALYZE === 'true',
46
+ });
47
+
48
+ module.exports = withBundleAnalyzer(baseConfig);
@@ -0,0 +1,60 @@
1
+ import '@/styles/globals.css';
2
+
3
+ import clsx from 'clsx';
4
+ import type { Metadata } from 'next';
5
+ import type { PropsWithChildren } from 'react';
6
+
7
+ import { siteConfig } from '@/lib/config';
8
+ import { fonts } from '@/lib/fonts';
9
+ import Footer from '@/ui/sections/Footer';
10
+ import Header from '@/ui/sections/Header';
11
+
12
+ export const metadata: Metadata = {
13
+ metadataBase: new URL(siteConfig.url),
14
+ title: {
15
+ default: siteConfig.title,
16
+ template: `%s | ${siteConfig.title}`,
17
+ },
18
+ description: siteConfig.description,
19
+ robots: { index: true, follow: true },
20
+ icons: {
21
+ icon: '/favicon/favicon.ico',
22
+ shortcut: '/favicon/favicon-16x16.png',
23
+ apple: '/favicon/apple-touch-icon.png',
24
+ },
25
+ openGraph: {
26
+ url: siteConfig.url,
27
+ title: siteConfig.title,
28
+ description: siteConfig.description,
29
+ siteName: siteConfig.title,
30
+ images: '/opengraph-image.png',
31
+ type: 'website',
32
+ locale: 'en_GB',
33
+ },
34
+ twitter: {
35
+ card: 'summary_large_image',
36
+ title: siteConfig.title,
37
+ description: siteConfig.description,
38
+ images: '/opengraph-image.png',
39
+ },
40
+ };
41
+
42
+ export default function RootLayout({ children }: PropsWithChildren) {
43
+ return (
44
+ <html lang="en" className="scroll-smooth break-words">
45
+ <body className={clsx(`min-h-screen antialiased`, fonts)}>
46
+ <div id="skiptocontent">
47
+ <a href="#main" className="sr-only focus:not-sr-only">
48
+ skip to main content
49
+ </a>
50
+ </div>
51
+
52
+ <div id="site-container">
53
+ <Header />
54
+ <main id="main">{children}</main>
55
+ <Footer />
56
+ </div>
57
+ </body>
58
+ </html>
59
+ );
60
+ }
@@ -0,0 +1,7 @@
1
+ export default function NotFound() {
2
+ return (
3
+ <>
4
+ <p>404 - Page not found</p>
5
+ </>
6
+ );
7
+ }
@@ -0,0 +1,7 @@
1
+ export default function Home() {
2
+ return (
3
+ <>
4
+ <p>Home page</p>
5
+ </>
6
+ );
7
+ }
@@ -0,0 +1,32 @@
1
+ import type { MetadataRoute } from 'next';
2
+
3
+ import { siteConfig } from '@/lib/config';
4
+
5
+ export default function robots(): MetadataRoute.Robots {
6
+ const isProduction = process.env.NODE_ENV === 'production';
7
+
8
+ if (!isProduction) {
9
+ // Block everything in development/staging
10
+ return {
11
+ rules: [
12
+ {
13
+ userAgent: '*',
14
+ disallow: '/',
15
+ },
16
+ ],
17
+ };
18
+ }
19
+
20
+ // Production rules
21
+ return {
22
+ rules: [
23
+ {
24
+ userAgent: '*',
25
+ allow: '/',
26
+ disallow: ['/api/', '/search', '/*?*utm_*', '/*?*ref=*'],
27
+ },
28
+ ],
29
+ sitemap: `${siteConfig.url}/sitemap.xml`,
30
+ host: siteConfig.url,
31
+ };
32
+ }
@@ -0,0 +1,22 @@
1
+ import type { MetadataRoute } from 'next';
2
+
3
+ import { siteConfig } from '@/lib/config';
4
+
5
+ export default function sitemap(): MetadataRoute.Sitemap {
6
+ const isProduction = process.env.NODE_ENV === 'production';
7
+
8
+ if (!isProduction) {
9
+ // Prevent search engines from indexing anything
10
+ return [];
11
+ }
12
+
13
+ return [
14
+ {
15
+ url: `${siteConfig.url}`,
16
+ lastModified: new Date(),
17
+ changeFrequency: 'daily',
18
+ priority: 0.7,
19
+ },
20
+ // Add more URLs here
21
+ ];
22
+ }
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 244 56.742">
2
+ <path d="M48.4.831h4.968v54.79H39.034V31.207L27.395 43.414h-1.561l-11.5-12.207v24.414H0V.831h5.11l21.433 23.7ZM94.393 48.663H72.817l-3.265 6.955H55.926v-1.984L80.482.263h6.1l24.7 53.371.994 1.987H97.515Zm-10.93-26.257-6.1 14.336H89.85ZM138.253 28.368c0-37.757 56.067-37.757 56.067 0 0 37.898-56.067 37.898-56.067 0Zm41.873 0c0-19.73-27.4-19.73-27.4 0-.137 19.732 27.4 19.732 27.4 0ZM244 53.492v1.987h-15.613l-9.368-16.04h-7.24v16.04h-13.91V.831h23.988c21.433.142 25.408 25.976 11.5 35.344Zm-22.285-40.028c-3.265-.142-6.671 0-9.936 0V27.09h9.936c7.665 0 7.954-13.626 0-13.626ZM120.651.973h14.194V33.62c0 8.517-2.555 14.762-7.523 18.595a22.3 22.3 0 0 1-10.5 3.974l-5.539-11.64a12.875 12.875 0 0 0 5.394-1.561c2.271-1.7 3.974-5.394 3.974-9.51Z"/>
3
+ </svg>
@@ -0,0 +1,29 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ // File to store the build ID
5
+ const BUILD_ID_FILE = path.join(process.cwd(), '.build-id');
6
+
7
+ /**
8
+ * Returns the build cache ID generated at the start of the build
9
+ * Falls back to current timestamp if no build ID file exists
10
+ */
11
+ export function getBuildCacheId(): number {
12
+ try {
13
+ if (fs.existsSync(BUILD_ID_FILE)) {
14
+ const storedId = fs.readFileSync(BUILD_ID_FILE, 'utf8').trim();
15
+ const buildId = parseInt(storedId, 10);
16
+
17
+ if (Number.isNaN(buildId)) {
18
+ throw new Error('Stored build ID is not a valid number');
19
+ }
20
+
21
+ return buildId;
22
+ }
23
+ } catch (error) {
24
+ console.warn('Error reading build ID:', error);
25
+ }
26
+
27
+ // Fallback to current timestamp if file doesn't exist or has invalid content
28
+ return Date.now();
29
+ }
@@ -0,0 +1,7 @@
1
+ export const siteConfig = {
2
+ siteName: 'Acorn',
3
+ title: 'Acorn - Nextjs Stater',
4
+ description: '',
5
+ locale: 'en',
6
+ url: process.env.NEXT_PUBLIC_SITE_URL || 'https://example.com',
7
+ };
File without changes
@@ -0,0 +1,15 @@
1
+ import { Inter, JetBrains_Mono } from 'next/font/google';
2
+
3
+ const fontHeading = JetBrains_Mono({
4
+ subsets: ['latin'],
5
+ variable: '--font-heading',
6
+ fallback: ['system-ui', 'arial'],
7
+ });
8
+
9
+ const fontSans = Inter({
10
+ subsets: ['latin'],
11
+ variable: '--font-sans',
12
+ fallback: ['system-ui', 'arial'],
13
+ });
14
+
15
+ export const fonts = [fontHeading.variable, fontSans.variable];
@@ -0,0 +1,124 @@
1
+ import type { Metadata } from 'next';
2
+
3
+ import { siteConfig } from '@/lib/config';
4
+
5
+ interface SeoComponent {
6
+ title: string;
7
+ description: string;
8
+ og_title: string;
9
+ og_description: string;
10
+ og_image: string;
11
+ twitter_title: string;
12
+ twitter_description: string;
13
+ twitter_image: string;
14
+ no_index?: boolean;
15
+ no_follow?: boolean;
16
+ }
17
+
18
+ interface SeoFallback {
19
+ title?: string;
20
+ description?: string;
21
+ }
22
+
23
+ interface Config {
24
+ slug?: string;
25
+ twitterCreator?: string;
26
+ googleVerificationId?: string;
27
+ siteName?: string;
28
+ fallback?: SeoFallback;
29
+ }
30
+
31
+ export const getMetaData = (
32
+ data?: SeoComponent | null,
33
+ config?: Config
34
+ ): Metadata => {
35
+ if (!config && !data) {
36
+ return {};
37
+ }
38
+
39
+ const { googleVerificationId, slug, twitterCreator, siteName, fallback } =
40
+ config || {};
41
+
42
+ const fallbackTitle = fallback?.title || '';
43
+ const fallbackDescription = fallback?.description || '';
44
+
45
+ const fallbackMetadata = {
46
+ metadataBase: new URL(
47
+ process.env.NEXT_PUBLIC_APP_URL || siteConfig.url
48
+ ),
49
+ title: fallbackTitle,
50
+ description: fallbackDescription,
51
+ alternates: {
52
+ canonical: slug,
53
+ },
54
+ openGraph: {
55
+ title: fallbackTitle,
56
+ description: fallbackDescription,
57
+ url: slug,
58
+ siteName,
59
+ type: 'website',
60
+ },
61
+ twitter: {
62
+ card: 'summary_large_image',
63
+ title: fallbackTitle,
64
+ description: fallbackDescription,
65
+ site: twitterCreator,
66
+ creator: twitterCreator,
67
+ },
68
+ ...(googleVerificationId && {
69
+ verification: {
70
+ google: `google-site-verification=${googleVerificationId}`,
71
+ },
72
+ }),
73
+ };
74
+
75
+ if (!data) {
76
+ return fallbackMetadata;
77
+ }
78
+
79
+ const {
80
+ title,
81
+ description,
82
+ og_title,
83
+ og_description,
84
+ og_image,
85
+ twitter_title,
86
+ twitter_description,
87
+ twitter_image,
88
+ } = data;
89
+
90
+ const customMetaData = {
91
+ title: title || fallbackTitle,
92
+ description: description || fallbackDescription,
93
+ openGraph: {
94
+ title: og_title || title || fallbackTitle,
95
+ description: og_description || description || fallbackDescription,
96
+ url: slug,
97
+ siteName,
98
+ ...(og_image && {
99
+ images: {
100
+ url: og_image,
101
+ },
102
+ }),
103
+ type: 'website',
104
+ },
105
+ twitter: {
106
+ card: 'summary_large_image',
107
+ title: twitter_title || og_title || title || fallbackTitle,
108
+ description:
109
+ twitter_description ||
110
+ og_description ||
111
+ description ||
112
+ fallbackDescription,
113
+ site: twitterCreator,
114
+ creator: twitterCreator,
115
+ ...(twitter_image && {
116
+ images: {
117
+ url: twitter_image || og_image,
118
+ },
119
+ }),
120
+ },
121
+ };
122
+
123
+ return { ...fallbackMetadata, ...customMetaData };
124
+ };
@@ -0,0 +1,12 @@
1
+ export const generateIDFromString = (string: string): string => {
2
+ return string
3
+ .toLowerCase()
4
+ .replace(/<[^>]+>/g, '')
5
+ .replace(/[^a-zA-Z ]/g, '')
6
+ .split(' ')
7
+ .join('-');
8
+ };
9
+
10
+ export const getCurrentTimestamp = (): number => {
11
+ return Date.now();
12
+ };
@@ -0,0 +1,23 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ .no-js {
7
+ @apply block;
8
+ }
9
+
10
+ .no-js .nojs-hidden {
11
+ @apply hidden;
12
+ }
13
+
14
+ .prose :where(a):not(:where([class~='not-prose'] *)) {
15
+ @apply underline;
16
+ }
17
+ }
18
+
19
+ @layer components {
20
+ }
21
+
22
+ @layer utilities {
23
+ }
@@ -0,0 +1,25 @@
1
+ import type {
2
+ DeepMap,
3
+ FieldError,
4
+ FieldValues,
5
+ Path,
6
+ RegisterOptions,
7
+ UseFormRegister,
8
+ } from 'react-hook-form';
9
+
10
+ export type ComponentThemes = 'default';
11
+
12
+ export type InputProps = {
13
+ id?: string;
14
+ label: string;
15
+ name: string;
16
+ className?: string;
17
+ type?: 'text' | 'email';
18
+ };
19
+
20
+ export type FormInputProps<TFormValues extends FieldValues = FieldValues> = {
21
+ name: Path<TFormValues>;
22
+ rules?: RegisterOptions;
23
+ register?: UseFormRegister<TFormValues>;
24
+ errors?: Partial<DeepMap<TFormValues, FieldError>>;
25
+ } & Omit<InputProps, 'name'>;
@@ -0,0 +1,3 @@
1
+ declare module '*.png';
2
+ declare module '*.jpg';
3
+ declare module '*.svg';
@@ -0,0 +1,13 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ const ConditionalWrapper = ({
4
+ condition,
5
+ wrapper,
6
+ children,
7
+ }: {
8
+ condition: boolean;
9
+ wrapper: (children: ReactNode) => ReactNode;
10
+ children: ReactNode;
11
+ }) => (condition ? wrapper(children) : children);
12
+
13
+ export default ConditionalWrapper;