@androbinco/library-cli 0.2.0 → 0.3.0
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/src/commands/add.js +107 -0
- package/src/commands/list.js +51 -0
- package/src/index.js +38 -73
- package/src/templates/carousel/components/navigation-buttons.tsx +1 -0
- package/src/templates/carousel/components/pagination/bullet.pagination.carousel.tsx +69 -0
- package/src/templates/carousel/components/pagination/number.pagination.carousel.tsx +30 -0
- package/src/templates/carousel/components/pagination/progress/progress.pagination.carousel.tsx +99 -0
- package/src/templates/carousel/components/pagination/progress/use-slide-progress.tsx +31 -0
- package/src/templates/carousel/components/pagination.tsx +47 -82
- package/src/templates/faqs-accordion/examples/faqs-showcase.tsx +42 -0
- package/src/templates/faqs-accordion/faqs-accordion.tsx +70 -0
- package/src/templates/faqs-accordion/mock-data.ts +38 -0
- package/src/templates/faqs-accordion/types.ts +18 -0
- package/src/templates/in-view/data.in-view.ts +1 -1
- package/src/templates/in-view/in-view-animation.tsx +7 -7
- package/src/templates/in-view/in-view-grid.tsx +22 -20
- package/src/templates/in-view/in-view-hidden-text.tsx +7 -7
- package/src/templates/in-view/in-view-stroke-line.tsx +6 -5
- package/src/templates/lenis/examples/providers.tsx +23 -0
- package/src/templates/lenis/lenis-provider.tsx +46 -0
- package/src/templates/scroll-components/hooks/use-client-dimensions.ts +21 -0
- package/src/templates/scroll-components/parallax/examples/parallax-showcase.tsx +87 -0
- package/src/templates/scroll-components/parallax/parallax.css +36 -0
- package/src/templates/scroll-components/parallax/parallax.tsx +67 -0
- package/src/templates/scroll-components/scale-gallery/components/expanding-element.tsx +40 -0
- package/src/templates/scroll-components/scale-gallery/examples/scale-gallery-showcase.tsx +68 -0
- package/src/templates/scroll-components/scale-gallery/scale-gallery.tsx +57 -0
- package/src/templates/scroll-components/scroll-tracker-showcase.tsx +44 -0
- package/src/templates/strapi-dynamic-zone/README.md +157 -0
- package/src/templates/strapi-dynamic-zone/dynamic-zone.tsx +113 -0
- package/src/templates/strapi-dynamic-zone/examples/page.tsx +53 -0
- package/src/templates/strapi-dynamic-zone/examples/renderers.tsx +74 -0
- package/src/templates/strapi-dynamic-zone/examples/types.ts +41 -0
- package/src/templates/strapi-dynamic-zone/index.ts +11 -0
- package/src/templates/strapi-dynamic-zone/types.ts +73 -0
- package/src/utils/components.js +187 -5
- package/src/utils/files.js +13 -12
- package/src/templates/scroll-tracker/examples/scroll-tracker-showcase.tsx +0 -90
- /package/src/templates/{scroll-tracker → scroll-components}/scroll-tracker-provider.tsx +0 -0
package/package.json
CHANGED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import {
|
|
4
|
+
COMPONENTS,
|
|
5
|
+
getComponentByName,
|
|
6
|
+
getVariantByName,
|
|
7
|
+
getFilesForVariant
|
|
8
|
+
} from '../utils/components.js';
|
|
9
|
+
import {
|
|
10
|
+
detectPackageManager,
|
|
11
|
+
checkMissingDependencies,
|
|
12
|
+
installDependencies
|
|
13
|
+
} from '../utils/dependencies.js';
|
|
14
|
+
import { copyComponent } from '../utils/files.js';
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
18
|
+
const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
|
|
19
|
+
|
|
20
|
+
export async function handleAdd(args) {
|
|
21
|
+
const [componentName, variantName] = args;
|
|
22
|
+
|
|
23
|
+
if (!componentName) {
|
|
24
|
+
console.error('Error: Component name is required');
|
|
25
|
+
console.log('Usage: add <component> [variant]');
|
|
26
|
+
console.log('Run "list" to see available components');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const component = getComponentByName(componentName);
|
|
31
|
+
|
|
32
|
+
if (!component) {
|
|
33
|
+
console.error(`Error: Component "${componentName}" not found`);
|
|
34
|
+
console.log('Available components:');
|
|
35
|
+
COMPONENTS.forEach(c => console.log(` - ${c.name}`));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Check if component requires variant
|
|
40
|
+
if (component.hasSubmenu && !variantName) {
|
|
41
|
+
console.error(`Error: Component "${componentName}" requires a variant`);
|
|
42
|
+
console.log('Available variants:');
|
|
43
|
+
component.submenuOptions.forEach(v => console.log(` - ${v.value}: ${v.description}`));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Get variant if specified
|
|
48
|
+
let variant = null;
|
|
49
|
+
let filesToCopy = null;
|
|
50
|
+
let examplesToCopy = [];
|
|
51
|
+
|
|
52
|
+
if (component.hasSubmenu && variantName) {
|
|
53
|
+
variant = getVariantByName(component, variantName);
|
|
54
|
+
|
|
55
|
+
if (!variant) {
|
|
56
|
+
console.error(`Error: Variant "${variantName}" not found for component "${componentName}"`);
|
|
57
|
+
console.log('Available variants:');
|
|
58
|
+
component.submenuOptions.forEach(v => console.log(` - ${v.value}: ${v.description}`));
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
filesToCopy = getFilesForVariant(component, variant);
|
|
63
|
+
examplesToCopy = variant.examples || [];
|
|
64
|
+
} else {
|
|
65
|
+
// Simple component without variant
|
|
66
|
+
examplesToCopy = component.examples || [];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Determine dependencies
|
|
70
|
+
let dependencies = component.dependencies || [];
|
|
71
|
+
|
|
72
|
+
// For ticker non-hover, no motion needed
|
|
73
|
+
if (component.name === 'ticker' && variantName === 'non-hover') {
|
|
74
|
+
dependencies = [];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check and install dependencies
|
|
78
|
+
if (dependencies.length > 0) {
|
|
79
|
+
const missingDependencies = await checkMissingDependencies(dependencies);
|
|
80
|
+
|
|
81
|
+
if (missingDependencies.length > 0) {
|
|
82
|
+
console.log(`Installing dependencies: ${missingDependencies.join(', ')}`);
|
|
83
|
+
const packageManager = await detectPackageManager();
|
|
84
|
+
const success = await installDependencies(missingDependencies, packageManager);
|
|
85
|
+
|
|
86
|
+
if (!success) {
|
|
87
|
+
console.warn('Warning: Failed to install some dependencies. You may need to install them manually.');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Copy component
|
|
93
|
+
console.log(`Copying ${componentName}${variantName ? ` (${variantName})` : ''}...`);
|
|
94
|
+
|
|
95
|
+
const success = await copyComponent(component, TEMPLATES_DIR, examplesToCopy, filesToCopy);
|
|
96
|
+
|
|
97
|
+
if (success) {
|
|
98
|
+
console.log(`✅ Component ${componentName} copied successfully!`);
|
|
99
|
+
console.log(`Files are available at: src/components/${componentName}/`);
|
|
100
|
+
if (examplesToCopy.length > 0) {
|
|
101
|
+
console.log(`Examples are available at: src/components/${componentName}/examples/`);
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
console.error(`❌ Failed to copy component ${componentName}`);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { COMPONENTS, getComponentByName } from '../utils/components.js';
|
|
2
|
+
|
|
3
|
+
export function handleList(args) {
|
|
4
|
+
const [componentName] = args;
|
|
5
|
+
|
|
6
|
+
if (!componentName) {
|
|
7
|
+
// List all components
|
|
8
|
+
console.log('Available components:\n');
|
|
9
|
+
|
|
10
|
+
for (const component of COMPONENTS) {
|
|
11
|
+
console.log(` ${component.name}`);
|
|
12
|
+
console.log(` ${component.description}`);
|
|
13
|
+
|
|
14
|
+
if (component.hasSubmenu) {
|
|
15
|
+
console.log(` Variants: ${component.submenuOptions.map(v => v.value).join(', ')}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
console.log('');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log('Usage:');
|
|
22
|
+
console.log(' add <component> Copy a component');
|
|
23
|
+
console.log(' add <component> <variant> Copy a specific variant');
|
|
24
|
+
console.log(' list <component> List variants for a component');
|
|
25
|
+
} else {
|
|
26
|
+
// List variants for specific component
|
|
27
|
+
const component = getComponentByName(componentName);
|
|
28
|
+
|
|
29
|
+
if (!component) {
|
|
30
|
+
console.error(`Error: Component "${componentName}" not found`);
|
|
31
|
+
console.log('Run "list" to see all available components');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log(`Component: ${component.name}`);
|
|
36
|
+
console.log(`Description: ${component.description}`);
|
|
37
|
+
console.log(`Dependencies: ${component.dependencies?.join(', ') || 'none'}`);
|
|
38
|
+
|
|
39
|
+
if (component.hasSubmenu) {
|
|
40
|
+
console.log('\nVariants:');
|
|
41
|
+
for (const variant of component.submenuOptions) {
|
|
42
|
+
console.log(` ${variant.value}`);
|
|
43
|
+
console.log(` ${variant.description}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(`\nUsage: add ${component.name} <variant>`);
|
|
47
|
+
} else {
|
|
48
|
+
console.log(`\nUsage: add ${component.name}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/index.js
CHANGED
|
@@ -4,83 +4,44 @@ import * as p from '@clack/prompts';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { checkReactDependency } from './utils/dependencies.js';
|
|
7
|
-
import { handleComponentsFlow } from './utils/components.js';
|
|
7
|
+
import { handleComponentsFlow, COMPONENTS } from './utils/components.js';
|
|
8
|
+
import { handleAdd } from './commands/add.js';
|
|
9
|
+
import { handleList } from './commands/list.js';
|
|
8
10
|
|
|
9
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
12
|
const __dirname = path.dirname(__filename);
|
|
11
13
|
const TEMPLATES_DIR = path.join(__dirname, 'templates');
|
|
12
14
|
|
|
13
|
-
//
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
hasNestedSubmenu: true,
|
|
44
|
-
nestedSubmenuOptions: [
|
|
45
|
-
{ value: 'in-view-hidden-text', file: 'in-view-hidden-text.tsx', required: true },
|
|
46
|
-
{ value: 'in-view-stroke-line', file: 'in-view-stroke-line.tsx', required: true },
|
|
47
|
-
{ value: 'in-view-animation', file: 'in-view-animation.tsx', required: false, checkExists: true },
|
|
48
|
-
{ value: 'data.in-view', file: 'data.in-view.ts', required: true }
|
|
49
|
-
],
|
|
50
|
-
examples: ['in-view-examples.home.tsx']
|
|
51
|
-
}
|
|
52
|
-
],
|
|
53
|
-
dependencies: ['motion']
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
name: 'ticker',
|
|
57
|
-
description: 'Ticker component with hover and non-hover variants',
|
|
58
|
-
sourceDir: 'ticker',
|
|
59
|
-
hasSubmenu: true,
|
|
60
|
-
submenuOptions: [
|
|
61
|
-
{
|
|
62
|
-
value: 'hover',
|
|
63
|
-
label: 'Stop on hover ticker (motion)',
|
|
64
|
-
description: 'MotionTicker with hover stop functionality',
|
|
65
|
-
examples: ['ticker-hover-showcase.home.tsx']
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
value: 'non-hover',
|
|
69
|
-
label: 'Non-stop on hover ticker (css)',
|
|
70
|
-
description: 'TickerStatic with CSS animations',
|
|
71
|
-
examples: ['ticker-static-showcase.home.tsx']
|
|
72
|
-
}
|
|
73
|
-
],
|
|
74
|
-
dependencies: ['motion']
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
name: 'scroll-tracker',
|
|
78
|
-
description: 'Scroll tracker provider for scroll-driven animations',
|
|
79
|
-
sourceDir: 'scroll-tracker',
|
|
80
|
-
dependencies: ['motion'],
|
|
81
|
-
examples: ['scroll-tracker-showcase.tsx']
|
|
82
|
-
}
|
|
83
|
-
];
|
|
15
|
+
// Parse command line arguments
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
const command = args[0];
|
|
18
|
+
|
|
19
|
+
// Handle direct commands (non-interactive mode)
|
|
20
|
+
if (command === 'add') {
|
|
21
|
+
await handleAdd(args.slice(1));
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (command === 'list') {
|
|
26
|
+
handleList(args.slice(1));
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (command === 'help' || command === '--help' || command === '-h') {
|
|
31
|
+
console.log('Robin Library CLI\n');
|
|
32
|
+
console.log('Usage:');
|
|
33
|
+
console.log(' robin-library-cli Interactive mode');
|
|
34
|
+
console.log(' robin-library-cli add <comp> Add a component');
|
|
35
|
+
console.log(' robin-library-cli list List all components');
|
|
36
|
+
console.log(' robin-library-cli list <comp> List variants for a component');
|
|
37
|
+
console.log(' robin-library-cli help Show this help\n');
|
|
38
|
+
console.log('Examples:');
|
|
39
|
+
console.log(' robin-library-cli add carousel');
|
|
40
|
+
console.log(' robin-library-cli add ticker hover');
|
|
41
|
+
console.log(' robin-library-cli add scroll-components parallax-image');
|
|
42
|
+
console.log(' robin-library-cli list ticker');
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
84
45
|
|
|
85
46
|
async function mainMenu() {
|
|
86
47
|
const action = await p.select({
|
|
@@ -118,7 +79,11 @@ async function main() {
|
|
|
118
79
|
}
|
|
119
80
|
|
|
120
81
|
if (action === 'components') {
|
|
121
|
-
await handleComponentsFlow(COMPONENTS, TEMPLATES_DIR);
|
|
82
|
+
const result = await handleComponentsFlow(COMPONENTS, TEMPLATES_DIR);
|
|
83
|
+
// If user selected go back, skip the "do more" prompt and show main menu again
|
|
84
|
+
if (result && result.goBack) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
122
87
|
}
|
|
123
88
|
|
|
124
89
|
const doMore = await p.select({
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { cva, VariantProps } from 'class-variance-authority';
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/common/utils/classname-builder';
|
|
4
|
+
|
|
5
|
+
import { useCarouselContext } from '../provider/carousel.provider';
|
|
6
|
+
|
|
7
|
+
type DotButtonVariants = VariantProps<typeof dotButtonVariants>['variant'];
|
|
8
|
+
|
|
9
|
+
export type DotPaginationProps = {
|
|
10
|
+
scrollSnaps: number[];
|
|
11
|
+
variant?: 'primary';
|
|
12
|
+
className?: string;
|
|
13
|
+
};
|
|
14
|
+
const dotButtonVariants = cva(
|
|
15
|
+
'size-3 cursor-pointer rounded-full transition-all duration-300 ease-out',
|
|
16
|
+
{
|
|
17
|
+
variants: {
|
|
18
|
+
variant: {
|
|
19
|
+
primary: 'focus:outline-2',
|
|
20
|
+
},
|
|
21
|
+
selected: {
|
|
22
|
+
true: 'w-9',
|
|
23
|
+
false: '',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
compoundVariants: [
|
|
27
|
+
{
|
|
28
|
+
variant: 'primary',
|
|
29
|
+
selected: true,
|
|
30
|
+
className: 'bg-indigo-900',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
variant: 'primary',
|
|
34
|
+
selected: false,
|
|
35
|
+
className: 'bg-indigo-500',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
export const DotButton: React.FC<{ index: number; variant: DotButtonVariants }> = ({
|
|
42
|
+
index,
|
|
43
|
+
variant = 'primary',
|
|
44
|
+
...restProps
|
|
45
|
+
}) => {
|
|
46
|
+
const { selectedIndex, onDotButtonClick } = useCarouselContext();
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<button
|
|
50
|
+
className={dotButtonVariants({ variant, selected: selectedIndex === index })}
|
|
51
|
+
disabled={selectedIndex === index}
|
|
52
|
+
onClick={() => onDotButtonClick(index)}
|
|
53
|
+
{...restProps}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const BulletPagination = ({
|
|
59
|
+
scrollSnaps,
|
|
60
|
+
className,
|
|
61
|
+
variant = 'primary',
|
|
62
|
+
...restProps
|
|
63
|
+
}: DotPaginationProps) => (
|
|
64
|
+
<div className={cn('mx-auto flex w-max gap-2', className)} {...restProps}>
|
|
65
|
+
{scrollSnaps.map((_, index) => (
|
|
66
|
+
<DotButton key={index} index={index} variant={variant} />
|
|
67
|
+
))}
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { VariantProps } from 'class-variance-authority';
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/common/utils/classname-builder';
|
|
4
|
+
import { Text } from '@/common/ui/text/text';
|
|
5
|
+
import { textVariants } from '@/common/ui/text/text.styles';
|
|
6
|
+
|
|
7
|
+
export type NumberPaginationProps = {
|
|
8
|
+
className?: string;
|
|
9
|
+
variant?: VariantProps<typeof textVariants>['variant'];
|
|
10
|
+
textClassName?: string;
|
|
11
|
+
selectedIndex: number;
|
|
12
|
+
scrollSnaps: number[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const NumberPagination = ({
|
|
16
|
+
className,
|
|
17
|
+
variant,
|
|
18
|
+
textClassName,
|
|
19
|
+
selectedIndex,
|
|
20
|
+
scrollSnaps,
|
|
21
|
+
...props
|
|
22
|
+
}: NumberPaginationProps) => {
|
|
23
|
+
return (
|
|
24
|
+
<div className={cn('mx-auto flex w-max gap-2', className)} {...props}>
|
|
25
|
+
<Text className={textClassName} variant={variant}>
|
|
26
|
+
{selectedIndex + 1} / {scrollSnaps.length}
|
|
27
|
+
</Text>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
};
|
package/src/templates/carousel/components/pagination/progress/progress.pagination.carousel.tsx
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { cva, VariantProps } from 'class-variance-authority';
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/common/utils/classname-builder';
|
|
4
|
+
|
|
5
|
+
import { useSlideProgress } from './use-slide-progress';
|
|
6
|
+
|
|
7
|
+
const effects = {
|
|
8
|
+
scale: (selectedIndex: number, totalItems: number) => ({
|
|
9
|
+
transformOrigin: 'left',
|
|
10
|
+
transform: `scaleX(${(selectedIndex + 1) / totalItems})`,
|
|
11
|
+
}),
|
|
12
|
+
slider: (selectedIndex: number, totalItems: number) => {
|
|
13
|
+
if (totalItems === 0) return {};
|
|
14
|
+
const width = Math.ceil(100 / totalItems);
|
|
15
|
+
const left = Math.ceil((selectedIndex / totalItems) * 100);
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
width: `${width}%`,
|
|
19
|
+
cursor: 'grab',
|
|
20
|
+
transformOrigin: 'left',
|
|
21
|
+
left: `${left}%`,
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type ProgressPaginationProps = {
|
|
27
|
+
className?: string;
|
|
28
|
+
selectedIndex: number;
|
|
29
|
+
scrollSnaps: number[];
|
|
30
|
+
effect?: keyof typeof effects;
|
|
31
|
+
onDotButtonClick: (index: number) => void;
|
|
32
|
+
variant?: VariantProps<typeof backgroundVariants>['variant'];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const backgroundVariants = cva('relative mx-auto flex w-200 max-w-full overflow-hidden', {
|
|
36
|
+
variants: {
|
|
37
|
+
variant: {
|
|
38
|
+
primary: 'h-2 rounded-full bg-grey-50-30',
|
|
39
|
+
},
|
|
40
|
+
effect: {
|
|
41
|
+
slider: 'bg-grey-50-6',
|
|
42
|
+
scale: 'bg-grey-50-30',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
const progressVariants = cva('linear absolute bottom-0 left-0 transition-all duration-300', {
|
|
47
|
+
variants: {
|
|
48
|
+
variant: {
|
|
49
|
+
primary: 'h-full rounded-full bg-indigo-500',
|
|
50
|
+
},
|
|
51
|
+
effect: {
|
|
52
|
+
slider: '',
|
|
53
|
+
scale: 'pointer-events-none !w-full',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export const ProgressPagination = ({
|
|
59
|
+
className,
|
|
60
|
+
selectedIndex,
|
|
61
|
+
scrollSnaps,
|
|
62
|
+
onDotButtonClick,
|
|
63
|
+
variant = 'primary',
|
|
64
|
+
effect = 'scale',
|
|
65
|
+
...props
|
|
66
|
+
}: ProgressPaginationProps) => {
|
|
67
|
+
const isSlider = effect === 'slider';
|
|
68
|
+
const { isDragging, barRef, setIsDragging } = useSlideProgress({ isActive: isSlider });
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div className={cn(backgroundVariants({ variant, effect }), className)} {...props}>
|
|
72
|
+
<button
|
|
73
|
+
ref={barRef}
|
|
74
|
+
className={cn(progressVariants({ variant, effect }))}
|
|
75
|
+
style={{ ...effects[effect](selectedIndex, scrollSnaps.length) }}
|
|
76
|
+
onMouseDown={() => {
|
|
77
|
+
if (!isSlider) return;
|
|
78
|
+
setIsDragging(true);
|
|
79
|
+
}}
|
|
80
|
+
onMouseUp={() => setIsDragging(false)}
|
|
81
|
+
/>
|
|
82
|
+
|
|
83
|
+
{scrollSnaps.map((_, index) => (
|
|
84
|
+
<div
|
|
85
|
+
key={index}
|
|
86
|
+
className={cn('h-full w-full', !isDragging && 'cursor-pointer')}
|
|
87
|
+
onMouseDown={() => {
|
|
88
|
+
onDotButtonClick(index);
|
|
89
|
+
if (!isSlider) return;
|
|
90
|
+
setIsDragging(true);
|
|
91
|
+
}}
|
|
92
|
+
onMouseEnter={() => {
|
|
93
|
+
if (isDragging) onDotButtonClick(index);
|
|
94
|
+
}}
|
|
95
|
+
/>
|
|
96
|
+
))}
|
|
97
|
+
</div>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export const useSlideProgress = ({ isActive }: { isActive: boolean }) => {
|
|
4
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
5
|
+
const barRef = useRef<HTMLButtonElement>(null);
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (!isActive) return;
|
|
9
|
+
const cancelDrag = () => setIsDragging(false);
|
|
10
|
+
|
|
11
|
+
if (isDragging) {
|
|
12
|
+
document.body.addEventListener('mouseup', cancelDrag);
|
|
13
|
+
document.body.addEventListener('touchend', cancelDrag);
|
|
14
|
+
barRef.current?.style.setProperty('cursor', 'grabbing');
|
|
15
|
+
document.body.style.setProperty('cursor', 'grabbing');
|
|
16
|
+
}
|
|
17
|
+
if (!isDragging) {
|
|
18
|
+
document.body.removeEventListener('mouseup', cancelDrag);
|
|
19
|
+
document.body.removeEventListener('touchend', cancelDrag);
|
|
20
|
+
document.body.style.setProperty('cursor', 'default');
|
|
21
|
+
barRef.current?.style.setProperty('cursor', 'grab');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return () => {
|
|
25
|
+
document.body.removeEventListener('mouseup', cancelDrag);
|
|
26
|
+
document.body.removeEventListener('touchend', cancelDrag);
|
|
27
|
+
};
|
|
28
|
+
}, [isDragging, isActive]);
|
|
29
|
+
|
|
30
|
+
return { isDragging, barRef, setIsDragging };
|
|
31
|
+
};
|
|
@@ -1,89 +1,54 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import React from 'react';
|
|
2
3
|
|
|
3
|
-
import { cva, VariantProps } from 'class-variance-authority';
|
|
4
|
-
|
|
5
|
-
import { cn } from '@/common/utils/classname-builder';
|
|
6
|
-
|
|
7
4
|
import { useCarouselContext } from './provider/carousel.provider';
|
|
8
5
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
import { BulletPagination, DotPaginationProps } from './pagination/bullet.pagination.carousel';
|
|
7
|
+
import { NumberPagination, NumberPaginationProps } from './pagination/number.pagination.carousel';
|
|
8
|
+
import {
|
|
9
|
+
ProgressPagination,
|
|
10
|
+
ProgressPaginationProps,
|
|
11
|
+
} from './pagination/progress/progress.pagination.carousel';
|
|
12
|
+
|
|
13
|
+
type OmitContext<T> = Omit<T, 'selectedIndex' | 'scrollSnaps' | 'onDotButtonClick'>;
|
|
14
|
+
type PaginationProps =
|
|
15
|
+
| ({ type: 'number' } & OmitContext<NumberPaginationProps>)
|
|
16
|
+
| ({ type: 'progress' } & OmitContext<ProgressPaginationProps>)
|
|
17
|
+
| ({ type: 'bullet' } & OmitContext<DotPaginationProps>);
|
|
18
|
+
|
|
19
|
+
export const Pagination = (props: PaginationProps) => {
|
|
20
|
+
const { type, variant, className, ...restProps } = props;
|
|
12
21
|
const { scrollSnaps, selectedIndex, onDotButtonClick } = useCarouselContext();
|
|
13
22
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
{
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
{
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
</div>
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
if (type === 'dot')
|
|
49
|
-
return (
|
|
50
|
-
<div className={cn('mx-auto flex w-max gap-2', className)} {...props}>
|
|
51
|
-
{scrollSnaps.map((_, index) => (
|
|
52
|
-
<DotButton key={index} index={index} variant="primary" />
|
|
53
|
-
))}
|
|
54
|
-
</div>
|
|
55
|
-
);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
type DotButtonVariants = VariantProps<typeof dotButtonVariants>['variant'];
|
|
59
|
-
const dotButtonVariants = cva(
|
|
60
|
-
'size-3 cursor-pointer rounded-full transition-all duration-300 ease-out focus:outline-2',
|
|
61
|
-
{
|
|
62
|
-
variants: {
|
|
63
|
-
variant: {
|
|
64
|
-
primary: 'bg-fill-brand-primary',
|
|
65
|
-
},
|
|
66
|
-
selected: {
|
|
67
|
-
true: 'w-9 !bg-amber-950',
|
|
68
|
-
false: '',
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
export const DotButton: React.FC<{ index: number; variant: DotButtonVariants }> = ({
|
|
75
|
-
index,
|
|
76
|
-
variant = 'primary',
|
|
77
|
-
...restProps
|
|
78
|
-
}) => {
|
|
79
|
-
const { selectedIndex, onDotButtonClick } = useCarouselContext();
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<button
|
|
83
|
-
className={dotButtonVariants({ variant, selected: selectedIndex === index })}
|
|
84
|
-
disabled={selectedIndex === index}
|
|
85
|
-
onClick={() => onDotButtonClick(index)}
|
|
86
|
-
{...restProps}
|
|
87
|
-
/>
|
|
88
|
-
);
|
|
23
|
+
const paginationComponents = {
|
|
24
|
+
number: () => (
|
|
25
|
+
<NumberPagination
|
|
26
|
+
className={className}
|
|
27
|
+
scrollSnaps={scrollSnaps}
|
|
28
|
+
selectedIndex={selectedIndex}
|
|
29
|
+
variant={variant as NumberPaginationProps['variant']}
|
|
30
|
+
{...restProps}
|
|
31
|
+
/>
|
|
32
|
+
),
|
|
33
|
+
progress: () => (
|
|
34
|
+
<ProgressPagination
|
|
35
|
+
className={className}
|
|
36
|
+
scrollSnaps={scrollSnaps}
|
|
37
|
+
selectedIndex={selectedIndex}
|
|
38
|
+
variant={variant as ProgressPaginationProps['variant']}
|
|
39
|
+
onDotButtonClick={onDotButtonClick}
|
|
40
|
+
{...restProps}
|
|
41
|
+
/>
|
|
42
|
+
),
|
|
43
|
+
bullet: () => (
|
|
44
|
+
<BulletPagination
|
|
45
|
+
className={className}
|
|
46
|
+
scrollSnaps={scrollSnaps}
|
|
47
|
+
variant={variant as DotPaginationProps['variant']}
|
|
48
|
+
{...restProps}
|
|
49
|
+
/>
|
|
50
|
+
),
|
|
51
|
+
} as const;
|
|
52
|
+
|
|
53
|
+
return paginationComponents[type]();
|
|
89
54
|
};
|