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 +1 -1
- package/setup/index.js +142 -66
- package/setup/templates/cypress/helpers/index.ts +57 -0
- package/setup/templates/cypress.config.ts +24 -0
- package/setup/templates/src/components/bitloops/button/Button.stories.tsx +34 -0
- package/setup/templates/src/components/bitloops/button/Button.tsx +22 -0
- /package/setup/templates/{src.components.bitloops.Unsupported.stories.tsx → src/components/bitloops/unsupported/Unsupported.stories.tsx} +0 -0
- /package/setup/templates/{src.components.bitloops.Unsupported.tsx → src/components/bitloops/unsupported/Unsupported.tsx} +0 -0
package/package.json
CHANGED
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
|
|
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(
|
|
95
|
-
const patchPackages = ''
|
|
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) => {
|
|
98
|
-
|
|
99
|
-
|
|
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', [
|
|
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'), {
|
|
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
|
-
|
|
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.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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(
|
|
187
|
-
this.destinationPath(
|
|
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) => {
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
200
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
235
|
-
if (this.options.storybook)
|
|
236
|
-
|
|
237
|
-
if (this.options.cypress)
|
|
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
|
+
}
|
|
File without changes
|