generator-bitloops 0.3.7 → 0.3.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "generator-bitloops",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "Next.js with TypeScript, Tailwind, Storybook and Cypress generator by Bitloops",
5
5
  "license": "MIT",
6
6
  "author": "Bitloops S.A.",
package/setup/index.js CHANGED
@@ -8,9 +8,11 @@ import { fileURLToPath } from 'url';
8
8
  const __filename = fileURLToPath(import.meta.url);
9
9
  const __dirname = path.dirname(__filename);
10
10
 
11
-
12
11
  function toKebabCase(str) {
13
- return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase().replace(/\s+/g, '-');
12
+ return str
13
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
14
+ .toLowerCase()
15
+ .replace(/\s+/g, '-');
14
16
  }
15
17
 
16
18
  export default class extends Generator {
@@ -68,7 +70,7 @@ export default class extends Generator {
68
70
  default: false,
69
71
  });
70
72
 
71
- this.installNextJS = async function() {
73
+ this.installNextJS = async function () {
72
74
  // Clone Next.js template with Tailwind if specified, using the project name
73
75
  const createNextAppCommand = ['-y', 'create-next-app@14.2.16'];
74
76
  createNextAppCommand.push(toKebabCase(this.options.project)); // Use the project name for the directory
@@ -80,49 +82,71 @@ export default class extends Generator {
80
82
  createNextAppCommand.push('@/*');
81
83
  createNextAppCommand.push('--use-npm');
82
84
  createNextAppCommand.push('--eslint');
83
-
85
+
84
86
  if (this.options.typescript) {
85
87
  createNextAppCommand.push('--typescript'); // This will avoid the TypeScript prompt
86
88
  } else {
87
89
  createNextAppCommand.push('--js');
88
90
  }
89
-
91
+
90
92
  if (this.options.tailwind) {
91
93
  createNextAppCommand.push('--tailwind');
92
94
  }
93
-
94
- this.log("Installing Next.js...");
95
- const patchPackages = '';//'next@14 react@18 react-dom@18';
95
+
96
+ this.log('Installing Next.js...');
97
+ const patchPackages = ''; //'next@14 react@18 react-dom@18';
96
98
  const additionalPackages = `react-tooltip ${patchPackages}`;
97
- await new Promise((resolve, error) => {exec(`npx ${createNextAppCommand.join(' ')} && cd ${toKebabCase(this.options.project)} && npm install ${additionalPackages}`).on('exit', (code) => {
98
- this.destinationRoot(this.destinationPath(toKebabCase(this.options.project)));
99
- resolve();
100
- });});
101
- }
99
+ await new Promise((resolve, error) => {
100
+ exec(
101
+ `npx ${createNextAppCommand.join(' ')} && cd ${toKebabCase(
102
+ this.options.project
103
+ )} && npm install ${additionalPackages}`
104
+ ).on('exit', (code) => {
105
+ this.destinationRoot(
106
+ this.destinationPath(toKebabCase(this.options.project))
107
+ );
108
+ resolve();
109
+ });
110
+ });
111
+ };
102
112
 
103
- this.installStorybook = function() {
113
+ this.installStorybook = function () {
104
114
  // Conditionally initialize Storybook
105
115
  if (this.options.storybook) {
106
116
  this.log('Installing Storybook...');
107
- this.spawnCommandSync('npx', ['-y', 'storybook@^8.4', 'init', '--no-dev']);
117
+ this.spawnCommandSync('npx', [
118
+ '-y',
119
+ 'storybook@^8.4',
120
+ 'init',
121
+ '--no-dev',
122
+ ]);
108
123
  this.log('Storybook installed!');
109
124
  // if (this.options.tailwind && this.options.storybook) {
110
125
  // Tailwind CSS specific setup for older versions of Storybook
111
126
  // this.spawnCommandSync('npx', ['storybook@latest', 'add', '@storybook/addon-styling-webpack']);
112
127
  // }
113
128
  }
114
- }
115
-
116
- this.installCypress = function() {
129
+ };
130
+
131
+ this.installCypress = function () {
117
132
  // Conditionally add Cypress
118
133
  if (this.options.cypress) {
119
134
  this.log('Installing Cypress...');
120
135
  this.spawnCommandSync('npm', ['install', '--save-dev', 'cypress']);
121
136
  this.log('Cypress installed!');
137
+ if (this.options.bitloops) {
138
+ this.spawnCommandSync('npm', [
139
+ 'install',
140
+ '--save-dev',
141
+ 'mochawesome',
142
+ 'mochawesome-merge',
143
+ 'mochawesome-report-generator',
144
+ ]);
145
+ }
122
146
  }
123
- }
124
-
125
- this.patchFiles = async function() {
147
+ };
148
+
149
+ this.patchFiles = async function () {
126
150
  // Conditionally initialize Storybook
127
151
  if (this.options.storybook) {
128
152
  this.log('Making Storybook changes...');
@@ -131,91 +155,134 @@ export default class extends Generator {
131
155
  this.log('Setting up Tailwind CSS with Storybook...');
132
156
  this.fs.copyTpl(
133
157
  this.templatePath('storybook.preview.ts'),
134
- this.destinationPath('.storybook/preview.ts'),
135
- );
158
+ this.destinationPath('.storybook/preview.ts')
159
+ );
136
160
  }
137
161
  this.log('Removing default Storybook stories...');
138
162
  try {
139
- fs.rmSync(this.destinationPath('src/stories'), { recursive: true, force: true });
163
+ fs.rmSync(this.destinationPath('src/stories'), {
164
+ recursive: true,
165
+ force: true,
166
+ });
140
167
  console.log('Sample stories directory deleted successfully!');
141
168
  } catch (err) {
142
- console.error('Error deleting sample stories directory:', err);
169
+ console.error('Error deleting sample stories directory:', err);
143
170
  }
144
171
  fs.unlinkSync(this.destinationPath('tailwind.config.ts'));
145
172
  this.fs.copyTpl(
146
173
  this.templatePath('tailwind.config.ts'),
147
- this.destinationPath('tailwind.config.ts'),
174
+ this.destinationPath('tailwind.config.ts')
148
175
  );
149
176
  }
150
-
177
+
151
178
  if (this.options.cypress) {
152
179
  this.log('Adding Cypress config...');
153
180
  this.fs.copyTpl(
154
181
  this.templatePath('cypress.config.ts'),
155
- this.destinationPath('cypress.config.ts'),
156
- );
182
+ this.destinationPath('cypress.config.ts')
183
+ );
157
184
  }
158
-
185
+
159
186
  fs.unlinkSync(this.destinationPath('src/app/page.tsx'));
160
187
  this.fs.copyTpl(
161
188
  this.templatePath('next.app.page.tsx'),
162
- this.destinationPath('src/app/page.tsx'),
163
- );
164
-
189
+ this.destinationPath('src/app/page.tsx')
190
+ );
191
+
165
192
  fs.unlinkSync(this.destinationPath('src/app/layout.tsx'));
166
193
  this.fs.copyTpl(
167
194
  this.templatePath('next.app.layout.tsx'),
168
195
  this.destinationPath('src/app/layout.tsx'),
169
- { projectName: this.options.project },
196
+ { projectName: this.options.project }
170
197
  );
171
-
198
+
172
199
  this.log('Adding Meyer reset in global.css...');
173
200
  fs.unlinkSync(this.destinationPath('src/app/globals.css'));
174
201
  this.fs.copyTpl(
175
202
  this.templatePath('globals.css'),
176
- this.destinationPath('src/app/globals.css'),
177
- );
203
+ this.destinationPath('src/app/globals.css')
204
+ );
178
205
 
179
- this.log('Adding Bitloops support components...');
180
- this.fs.copyTpl(
181
- this.templatePath('src.components.bitloops.Unsupported.tsx'),
182
- this.destinationPath('src/components/bitloops/Unsupported.tsx'),
183
- );
184
- if (this.options.storybook) {
206
+ if (this.options.bitloops) {
207
+ this.log('Adding Bitloops support components...');
208
+ const unsupportedPath =
209
+ 'src/components/bitloops/unsupported/Unsupported.tsx';
185
210
  this.fs.copyTpl(
186
- this.templatePath('src.components.bitloops.Unsupported.stories.tsx'),
187
- this.destinationPath('src/components/bitloops/Unsupported.stories.tsx'),
188
- );
211
+ this.templatePath(unsupportedPath),
212
+ this.destinationPath(unsupportedPath)
213
+ );
214
+ const buttonPath = 'src/components/bitloops/button/Button.tsx';
215
+ this.fs.copyTpl(
216
+ this.templatePath(buttonPath),
217
+ this.destinationPath(buttonPath)
218
+ );
219
+ if (this.options.storybook) {
220
+ const unsupportedPath =
221
+ 'src/components/bitloops/unsupported/Unsupported.stories.tsx';
222
+ this.fs.copyTpl(
223
+ this.templatePath(unsupportedPath),
224
+ this.destinationPath(unsupportedPath)
225
+ );
226
+ const buttonPath =
227
+ 'src/components/bitloops/button/Button.stories.tsx';
228
+ this.fs.copyTpl(
229
+ this.templatePath(buttonPath),
230
+ this.destinationPath(buttonPath)
231
+ );
232
+ }
233
+ if (this.options.cypress) {
234
+ const path = 'cypress/helpers/index.ts';
235
+ this.fs.copyTpl(this.templatePath(path), this.destinationPath(path));
236
+ }
237
+ this.spawnCommandSync('npm', [
238
+ 'install',
239
+ '--save-dev',
240
+ 'react-aria-components',
241
+ ]);
189
242
  }
190
- }
243
+ };
191
244
 
192
- this.commitChanges = async function() {
245
+ this.commitChanges = async function () {
193
246
  this.log('Committing changes to git...');
194
- await new Promise((resolve) => {exec(`cd ${toKebabCase(this.options.project)} && git add . && git commit -m "Initial setup"`).on('exit', (code) => {
195
- if (code !== 0) {
196
- this.log('Error committing changes to git! ', code);
247
+ await new Promise((resolve) => {
248
+ exec(
249
+ `cd ${toKebabCase(
250
+ this.options.project
251
+ )} && git add . && git commit -m "Initial setup"`
252
+ ).on('exit', (code) => {
253
+ if (code !== 0) {
254
+ this.log('Error committing changes to git! ', code);
255
+ resolve();
256
+ }
257
+ this.log('Git changes committed!');
197
258
  resolve();
198
- }
199
- this.log('Git changes committed!');
200
- resolve();
201
- });});
202
- }
259
+ });
260
+ });
261
+ };
203
262
  }
204
263
 
205
264
  initializing() {
206
265
  // Check if the project name and --nextjs flag are provided
207
266
  if (!this.options.project) {
208
- this.log("Error: --project option is required to specify the project name.");
267
+ this.log(
268
+ 'Error: --project option is required to specify the project name.'
269
+ );
209
270
  process.exit(1);
210
271
  }
211
272
 
212
273
  if (!this.options.nextjs) {
213
- this.log("Error: --nextjs option is currently required to scaffold a project.");
274
+ this.log(
275
+ 'Error: --nextjs option is currently required to scaffold a project.'
276
+ );
214
277
  process.exit(1);
215
278
  }
216
279
 
217
- this.log(`Initializing project ${toKebabCase(this.options.project)} with the selected options...`);
218
- }
280
+ this.log(
281
+ `Initializing project ${toKebabCase(
282
+ this.options.project
283
+ )} with the selected options...`
284
+ );
285
+ }
219
286
 
220
287
  async main() {
221
288
  await this.installNextJS();
@@ -228,12 +295,21 @@ export default class extends Generator {
228
295
  }
229
296
 
230
297
  end() {
231
- this.log(`Your Bitloops project '${toKebabCase(this.options.project)}' setup is complete! 🎉🎉🎉`);
298
+ this.log(
299
+ `Your Bitloops project '${toKebabCase(
300
+ this.options.project
301
+ )}' setup is complete! 🎉🎉🎉`
302
+ );
232
303
  this.log('');
233
304
  this.log('Use the following commands to start:');
234
- this.log("- `npm run dev` to start the Next.js app.");
235
- if (this.options.storybook) this.log("- `npm run storybook` to start Storybook.");
236
- if (this.options.cypress) this.log("- `npx cypress open --e2e --browser chrome` to open Cypress.");
237
- if (this.options.cypress) this.log("- `npx cypress run --e2e --browser chrome` to run Cypress on the terminal.");
305
+ this.log('- `npm run dev` to start the Next.js app.');
306
+ if (this.options.storybook)
307
+ this.log('- `npm run storybook` to start Storybook.');
308
+ if (this.options.cypress)
309
+ this.log('- `npx cypress open --e2e --browser chrome` to open Cypress.');
310
+ if (this.options.cypress)
311
+ this.log(
312
+ '- `npx cypress run --e2e --browser chrome` to run Cypress on the terminal.'
313
+ );
238
314
  }
239
- };
315
+ }
@@ -0,0 +1,57 @@
1
+ export const getWindowBounds = (window: Window): DOMRect => {
2
+ return new DOMRect(0, 0, window.innerWidth, window.innerHeight);
3
+ };
4
+
5
+ export const isOutOfBounds = (inRect: DOMRect, outRect: DOMRect): boolean => {
6
+ const isOut =
7
+ inRect.left < outRect.left ||
8
+ inRect.right > outRect.right ||
9
+ inRect.bottom > outRect.bottom ||
10
+ inRect.top < outRect.top;
11
+ return isOut;
12
+ };
13
+
14
+ export const hasHorizontalScroll = (document: Document): boolean => {
15
+ const scrollingElement = document.scrollingElement;
16
+ if (!scrollingElement) {
17
+ return false;
18
+ }
19
+ const scrollWidth = scrollingElement.scrollWidth || 0;
20
+ const clientWidth = scrollingElement.clientWidth || 0;
21
+
22
+ return scrollWidth > clientWidth;
23
+ };
24
+
25
+ export const areVerticalLeftAligned = (rects: DOMRect[]): boolean => {
26
+ const firstRect = rects[0];
27
+ for (let i = 1; i < rects.length; i++) {
28
+ if (rects[i].x !== firstRect.x) {
29
+ return false;
30
+ }
31
+ }
32
+ return true;
33
+ };
34
+
35
+ export const areVerticalCenteredAligned = (rects: DOMRect[]): boolean => {
36
+ const firstRect = rects[0];
37
+ const xCenterOfFirstRect = firstRect.x + firstRect.width / 2;
38
+ for (let i = 1; i < rects.length; i++) {
39
+ const xCenterOfRect = rects[i].x + rects[i].width / 2;
40
+ if (xCenterOfRect !== xCenterOfFirstRect) {
41
+ return false;
42
+ }
43
+ }
44
+ return true;
45
+ };
46
+
47
+ export const areVerticalRightAligned = (rects: DOMRect[]): boolean => {
48
+ const firstRect = rects[0];
49
+ const xRightOfFirstRect = firstRect.x + firstRect.width;
50
+ for (let i = 1; i < rects.length; i++) {
51
+ const xRightOfRect = rects[i].x + rects[i].width;
52
+ if (xRightOfRect !== xRightOfFirstRect) {
53
+ return false;
54
+ }
55
+ }
56
+ return true;
57
+ };
@@ -1,8 +1,32 @@
1
1
  import { defineConfig } from 'cypress';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
2
4
 
3
5
  export default defineConfig({
4
6
  e2e: {
5
7
  specPattern: "**/*.cy.{ts,tsx}",
6
8
  supportFile: false,
9
+ testIsolation: false,
10
+ setupNodeEvents(on) {
11
+ on('task', {
12
+ saveTestResults(results) {
13
+ const resultsPath = path.join(__dirname, '.', 'cypress', 'results', 'results.json');
14
+ if (!fs.existsSync(resultsPath)) {
15
+ fs.writeFileSync(resultsPath, '[]'); // Create file if it doesn't exist
16
+ }
17
+ const existingResults = JSON.parse(fs.readFileSync(resultsPath, 'utf8'));
18
+ existingResults.push(JSON.parse(results));
19
+ fs.writeFileSync(resultsPath, JSON.stringify(existingResults, null, 2));
20
+ return null; // Indicate the task was successful
21
+ }
22
+ });
23
+ },
7
24
  },
25
+ reporter: "mochawesome",
26
+ reporterOptions: {
27
+ reportDir: "cypress/results",
28
+ overwrite: true,
29
+ html: true,
30
+ json: true,
31
+ },
8
32
  });
@@ -0,0 +1,34 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import { ButtonElement, ButtonElementProps } from './Button';
3
+
4
+ const meta: Meta<typeof ButtonElement> = {
5
+ title: 'Bitloops/Button',
6
+ component: ButtonElement,
7
+ parameters: {
8
+ layout: 'centered',
9
+ },
10
+ tags: ['autodocs'],
11
+ };
12
+
13
+ export default meta;
14
+
15
+ const props: ButtonElementProps = {
16
+ disabled: false,
17
+ onPress: () => {
18
+ console.log('Button pressed');
19
+ },
20
+ className:
21
+ 'border-purple-700 bg-purple-700 border opacity-100 w-40 flex-col items-center border-solid',
22
+ children: (
23
+ <p className='text-white opacity-100 text-lg font-mono'>Hello World!</p>
24
+ ),
25
+ };
26
+
27
+ type Story = StoryObj<typeof ButtonElement>;
28
+
29
+ export const Default: Story = {
30
+ args: props,
31
+ parameters: {
32
+ layout: 'fullscreen',
33
+ },
34
+ };
@@ -0,0 +1,22 @@
1
+ import { Button as AriaButton } from 'react-aria-components';
2
+
3
+ export type ButtonElementProps = {
4
+ name?: string;
5
+ disabled?: boolean;
6
+ onPress: (e?: unknown) => void;
7
+ children?: React.ReactNode;
8
+ className?: string;
9
+ };
10
+ export function ButtonElement(props: ButtonElementProps) {
11
+ const { name, disabled, children, className, onPress } = props;
12
+ return (
13
+ <AriaButton
14
+ name={name}
15
+ isDisabled={disabled}
16
+ onPress={onPress}
17
+ className={className}
18
+ >
19
+ {children}
20
+ </AriaButton>
21
+ );
22
+ }