mautourco-components 0.2.8 → 0.2.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": "mautourco-components",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "private": false,
5
5
  "description": "Bibliothèque de composants Motorco pour le redesign",
6
6
  "main": "dist/index.js",
@@ -14,7 +14,6 @@ import EyeIcon from "./icons/EyeIcon";
14
14
  import InfoIcon from "./icons/InfoIcon";
15
15
  import CheckCircleIcon from "./icons/CheckCircleIcon";
16
16
  import StrollerIcon from "./icons/StrollerIcon";
17
- import ChevronDownIcon from "./icons/ChevronDownIcon";
18
17
  import CarIcon from "./icons/CarIcon";
19
18
  import MoreIcon from "./icons/MoreIcon";
20
19
  import Settings from "./icons/Settings";
@@ -74,6 +73,7 @@ interface IconProps {
74
73
  size?: "xs" | "sm" | "md" | "lg" | "xl";
75
74
  className?: string;
76
75
  color?: string;
76
+ style?: React.CSSProperties;
77
77
  }
78
78
 
79
79
  const Icon: React.FC<IconProps> = ({
@@ -81,8 +81,9 @@ const Icon: React.FC<IconProps> = ({
81
81
  size = "md",
82
82
  className = "",
83
83
  color,
84
+ style,
84
85
  }) => {
85
- const iconProps = { size, className, color };
86
+ const iconProps = { size, className, color, style };
86
87
 
87
88
  switch (name) {
88
89
  case "chevron-down":
@@ -124,7 +125,7 @@ const Icon: React.FC<IconProps> = ({
124
125
  case "stroller":
125
126
  return <StrollerIcon {...iconProps} />;
126
127
  case "chevron-down-new":
127
- return <ChevronDownIcon {...iconProps} />;
128
+ return <Chevron direction="down" variant="filled" {...iconProps} />;
128
129
  case "car":
129
130
  return <CarIcon {...iconProps} />;
130
131
  case "more":
@@ -2,16 +2,20 @@ import React from 'react';
2
2
 
3
3
  interface ChevronProps {
4
4
  direction?: 'up' | 'down' | 'left' | 'right';
5
+ variant?: 'outline' | 'filled';
5
6
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
6
7
  className?: string;
7
8
  color?: string;
9
+ style?: React.CSSProperties;
8
10
  }
9
11
 
10
12
  const Chevron: React.FC<ChevronProps> = ({
11
13
  direction = 'down',
14
+ variant = 'outline',
12
15
  size = 'md',
13
16
  className = '',
14
- color
17
+ color,
18
+ style
15
19
  }) => {
16
20
  const getSizeClasses = () => {
17
21
  switch (size) {
@@ -24,14 +28,13 @@ const Chevron: React.FC<ChevronProps> = ({
24
28
  }
25
29
  };
26
30
 
27
- const getDirectionStyle = () => {
28
- switch (direction) {
29
- case 'up': return { transform: 'rotate(0deg)' };
30
- case 'down': return { transform: 'rotate(0deg)' };
31
- case 'left': return { transform: 'rotate(360deg)' };
32
- case 'right': return { transform: 'rotate(360deg)' };
33
- default: return { transform: 'rotate(0deg)' };
31
+ const getRotationStyle = () => {
32
+ if (variant === 'filled') {
33
+ const rotations = { up: 180, down: 0, left: 90, right: -90 };
34
+ return { transform: `rotate(${rotations[direction] || 0}deg)` };
34
35
  }
36
+ // Outline variant doesn't need rotation (paths handle direction)
37
+ return { transform: 'rotate(0deg)' };
35
38
  };
36
39
 
37
40
  const getPath = () => {
@@ -50,14 +53,30 @@ const Chevron: React.FC<ChevronProps> = ({
50
53
  };
51
54
 
52
55
  const sizeClasses = getSizeClasses();
53
- const directionStyle = getDirectionStyle();
54
56
  const colorClass = color ? `text-${color}` : 'text-current';
55
57
  const classes = `${sizeClasses} ${colorClass} ${className}`;
58
+ const rotationStyle = getRotationStyle();
59
+ const combinedStyle = style ? { ...rotationStyle, ...style } : rotationStyle;
56
60
 
57
- return (
61
+ return variant === 'filled' ? (
62
+ <svg
63
+ className={classes}
64
+ style={combinedStyle}
65
+ viewBox="0 0 15 12"
66
+ fill="none"
67
+ xmlns="http://www.w3.org/2000/svg"
68
+ >
69
+ <path
70
+ d="M12.4707 0.571533H2.07621C0.87817 0.571533 0.163582 1.90675 0.828137 2.90358L6.02536 10.6994C6.6191 11.59 7.92778 11.59 8.52151 10.6994L13.7187 2.90358C14.3833 1.90675 13.6687 0.571533 12.4707 0.571533Z"
71
+ fill="currentColor"
72
+ stroke="currentColor"
73
+ strokeWidth="1.14286"
74
+ />
75
+ </svg>
76
+ ) : (
58
77
  <svg
59
78
  className={classes}
60
- style={directionStyle}
79
+ style={combinedStyle}
61
80
  viewBox="0 0 24 24"
62
81
  fill="none"
63
82
  stroke="currentColor"
@@ -0,0 +1,179 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import Icon from '../../atoms/Icon/Icon';
3
+ import { Text } from '../../atoms/Typography/Typography';
4
+ import '../../../styles/components/molecule/service-selector.css';
5
+
6
+ export type ServiceType = 'transfer' | 'accommodation' | 'excursion';
7
+
8
+ export interface ServiceOption {
9
+ value: ServiceType;
10
+ label: string;
11
+ icon: string;
12
+ disabled?: boolean;
13
+ badge?: string;
14
+ }
15
+
16
+ export interface ServiceSelectorProps {
17
+ /** Selected service */
18
+ value?: ServiceType;
19
+ /** Change handler */
20
+ onChange: (value: ServiceType) => void;
21
+ /** Disabled state */
22
+ disabled?: boolean;
23
+ /** Additional CSS classes */
24
+ className?: string;
25
+ }
26
+
27
+ const DEFAULT_OPTIONS: ServiceOption[] = [
28
+ {
29
+ value: 'accommodation',
30
+ label: 'Accommodation',
31
+ icon: 'building',
32
+ },
33
+ {
34
+ value: 'transfer',
35
+ label: 'Transfer',
36
+ icon: 'car',
37
+ },
38
+ {
39
+ value: 'excursion',
40
+ label: 'Excursion',
41
+ icon: 'map-pin',
42
+ disabled: true,
43
+ badge: 'Coming soon',
44
+ },
45
+ ];
46
+
47
+ const ServiceSelector: React.FC<ServiceSelectorProps> = ({
48
+ value,
49
+ onChange,
50
+ disabled = false,
51
+ className = '',
52
+ }) => {
53
+ const [isOpen, setIsOpen] = useState(false);
54
+ const dropdownRef = useRef<HTMLDivElement>(null);
55
+
56
+ // Close dropdown when clicking outside
57
+ useEffect(() => {
58
+ const handleClickOutside = (event: MouseEvent) => {
59
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
60
+ setIsOpen(false);
61
+ }
62
+ };
63
+
64
+ document.addEventListener('mousedown', handleClickOutside);
65
+ return () => {
66
+ document.removeEventListener('mousedown', handleClickOutside);
67
+ };
68
+ }, []);
69
+
70
+ const handleToggleDropdown = () => {
71
+ if (!disabled) {
72
+ setIsOpen(!isOpen);
73
+ }
74
+ };
75
+
76
+ const handleOptionSelect = (option: ServiceOption) => {
77
+ if (disabled || option.disabled) return;
78
+ onChange(option.value);
79
+ setIsOpen(false);
80
+ };
81
+
82
+ const getDropdownState = () => {
83
+ if (disabled) return 'disabled';
84
+ // If a service is selected, always show selected state (even if open)
85
+ if (value) return 'selected';
86
+ if (isOpen) return 'open';
87
+ return 'default';
88
+ };
89
+
90
+ const selectedOption = value ? DEFAULT_OPTIONS.find(opt => opt.value === value) : null;
91
+ const displayText = selectedOption ? selectedOption.label : 'Select a service';
92
+
93
+ return (
94
+ <div ref={dropdownRef} className={`service-selector ${className}`}>
95
+ <button
96
+ type="button"
97
+ className={`service-selector__trigger service-selector__trigger--${getDropdownState()}`}
98
+ onClick={handleToggleDropdown}
99
+ disabled={disabled}
100
+ aria-expanded={isOpen}
101
+ aria-haspopup="listbox"
102
+ >
103
+ <div className="service-selector__trigger-content">
104
+ {selectedOption && (
105
+ <Icon
106
+ name={selectedOption.icon as any}
107
+ size="md"
108
+ className="service-selector__trigger-icon"
109
+ />
110
+ )}
111
+ <Text
112
+ size="base"
113
+ variant="bold"
114
+ className="service-selector__trigger-text"
115
+ >
116
+ {displayText}
117
+ </Text>
118
+ </div>
119
+ <Icon
120
+ name="chevron-down-new"
121
+ size="sm"
122
+ className="service-selector__trigger-chevron"
123
+ style={{
124
+ transform: isOpen ? 'rotate(180deg)' : 'rotate(0deg)',
125
+ transition: 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
126
+ transformOrigin: 'center'
127
+ }}
128
+ />
129
+ </button>
130
+
131
+ {isOpen && (
132
+ <div className="service-selector__panel" role="listbox">
133
+ <div className="service-selector__content">
134
+ <div className="service-selector__options-wrapper">
135
+ {DEFAULT_OPTIONS.map((option) => {
136
+ const isSelected = value === option.value;
137
+ const isDisabled = option.disabled || disabled;
138
+
139
+ return (
140
+ <button
141
+ key={option.value}
142
+ type="button"
143
+ className={`service-selector__option ${isSelected ? 'service-selector__option--selected' : ''} ${isDisabled ? 'service-selector__option--disabled' : ''}`}
144
+ onClick={() => handleOptionSelect(option)}
145
+ disabled={isDisabled}
146
+ role="option"
147
+ aria-selected={isSelected}
148
+ >
149
+ <Icon
150
+ name={option.icon as any}
151
+ size="md"
152
+ className={`service-selector__option-icon ${isSelected ? 'service-selector__option-icon--selected' : ''}`}
153
+ />
154
+ <Text
155
+ size="base"
156
+ variant="bold"
157
+ color={isSelected ? 'inverted' : 'default'}
158
+ className="service-selector__option-text"
159
+ >
160
+ {option.label}
161
+ </Text>
162
+ {option.badge && (
163
+ <span className="service-selector__option-badge">
164
+ {option.badge}
165
+ </span>
166
+ )}
167
+ </button>
168
+ );
169
+ })}
170
+ </div>
171
+ </div>
172
+ </div>
173
+ )}
174
+ </div>
175
+ );
176
+ };
177
+
178
+ export default ServiceSelector;
179
+
@@ -0,0 +1,3 @@
1
+ export { default } from './ServiceSelector';
2
+ export type { ServiceSelectorProps, ServiceType, ServiceOption } from './ServiceSelector';
3
+
@@ -0,0 +1,228 @@
1
+ /* Service Selector Component Styles */
2
+
3
+ .service-selector {
4
+ position: relative;
5
+ width: 100%;
6
+ }
7
+
8
+ /* Trigger Button */
9
+ .service-selector__trigger {
10
+ width: 100%;
11
+ padding: var(--service-selector-spacing-service-selector-padding-y, 12px) var(--service-selector-spacing-service-selector-padding-x, 24px);
12
+ background-color: var(--service-selector-color-service-selector-background-default, #ffffff);
13
+ border: var(--service-selector-border-width-default, 1px) solid var(--service-selector-color-service-selector-border-default, #a3a3a3);
14
+ border-radius: var(--border-radius-rounded-xl, 12px);
15
+ opacity: var(--opacity-opacity-100, 1);
16
+ display: flex;
17
+ align-items: center;
18
+ justify-content: space-between;
19
+ gap: var(--service-selector-spacing-service-selector-gap, 40px);
20
+ cursor: pointer;
21
+ transition: all 0.2s ease-in-out;
22
+ /* Website/Light/Level 1 */
23
+ box-shadow: var(--spacing-base-0, 0px) var(--spacing-base-0, 0px) var(--backdrop-blur-backdrop-blur-sm, 4px) var(--spacing-base-0, 0px) rgba(48, 54, 66, 0.11), var(--spacing-base-0, 0px) var(--spacing-base-0-5, 2px) var(--backdrop-blur-backdrop-blur-xs, 2px) var(--spacing-base-0, 0px) rgba(48, 54, 66, 0.10);
24
+ }
25
+
26
+ .service-selector__trigger:hover:not(:disabled):not(.service-selector__trigger--selected) {
27
+ background-color: var(--service-selector-color-service-selector-background-hover, #ccfbf6);
28
+ border-color: var(--service-selector-color-service-selector-border-hover, #0f7173);
29
+ }
30
+
31
+ .service-selector__trigger-content {
32
+ display: flex;
33
+ align-items: center;
34
+ gap: var(--service-selector-spacing-dropdown-item-gap, 12px);
35
+ flex: 1;
36
+ min-width: 0;
37
+ }
38
+
39
+ .service-selector__trigger-icon {
40
+ flex-shrink: 0;
41
+ color: var(--service-selector-color-service-selector-foreground-default, #0f7173);
42
+ }
43
+
44
+ .service-selector__trigger-text {
45
+ flex-shrink: 0;
46
+ color: var(--service-selector-color-service-selector-foreground-default, #0f7173);
47
+ }
48
+
49
+ .service-selector__trigger-chevron {
50
+ flex-shrink: 0;
51
+ display: inline-flex;
52
+ align-items: center;
53
+ justify-content: center;
54
+ color: var(--service-selector-color-service-selector-foreground-default, #0f7173);
55
+ }
56
+
57
+ .service-selector__trigger-chevron svg {
58
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
59
+ transform-origin: center;
60
+ }
61
+
62
+ /* Trigger States */
63
+ .service-selector__trigger--default {
64
+ background-color: var(--service-selector-color-service-selector-background-default, #ffffff);
65
+ border-color: var(--service-selector-color-service-selector-border-default, #a3a3a3);
66
+ }
67
+
68
+ .service-selector__trigger--default .service-selector__trigger-icon,
69
+ .service-selector__trigger--default .service-selector__trigger-text,
70
+ .service-selector__trigger--default .service-selector__trigger-chevron {
71
+ color: var(--service-selector-color-service-selector-foreground-default, #0f7173);
72
+ }
73
+
74
+ .service-selector__trigger--open {
75
+ border-color: var(--service-selector-color-service-selector-border-hover, #0f7173);
76
+ }
77
+
78
+ /* Selected state - same style as default */
79
+ .service-selector__trigger.service-selector__trigger--selected {
80
+ background-color: var(--service-selector-color-service-selector-background-default, #ffffff);
81
+ border-color: var(--service-selector-color-service-selector-border-default, #a3a3a3);
82
+ }
83
+
84
+ .service-selector__trigger.service-selector__trigger--selected .service-selector__trigger-icon,
85
+ .service-selector__trigger.service-selector__trigger--selected .service-selector__trigger-text,
86
+ .service-selector__trigger.service-selector__trigger--selected .service-selector__trigger-chevron {
87
+ color: var(--service-selector-color-service-selector-foreground-default, #0f7173);
88
+ }
89
+
90
+ /* When dropdown is open AND a service is selected, keep default style */
91
+ .service-selector__trigger.service-selector__trigger--selected:hover {
92
+ background-color: var(--service-selector-color-service-selector-background-hover, #ccfbf6);
93
+ border-color: var(--service-selector-color-service-selector-border-hover, #0f7173);
94
+ }
95
+
96
+ .service-selector__trigger--disabled {
97
+ opacity: 0.6;
98
+ cursor: not-allowed;
99
+ }
100
+
101
+ .service-selector__trigger--disabled .service-selector__trigger-icon,
102
+ .service-selector__trigger--disabled .service-selector__trigger-chevron {
103
+ color: var(--color-neutral-400, #a3a3a3);
104
+ }
105
+
106
+ /* Dropdown Panel */
107
+ .service-selector__panel {
108
+ position: absolute;
109
+ top: 100%;
110
+ left: 0;
111
+ right: 0;
112
+ margin-top: 8px;
113
+ background-color: var(--color-elevation-level-1, #ffffff);
114
+ border-radius: var(--service-selector-boder-radius-dropdown-item-default, 12px);
115
+ overflow: hidden;
116
+ z-index: 50;
117
+ border: var(--service-selector-border-width-default, 1px) solid var(--service-selector-color-service-selector-border-default, #a3a3a3);
118
+ box-shadow: 0px 0px 4px 0px rgba(48, 54, 66, 0.11), 0px 0px 2px 0px rgba(48, 54, 66, 0.1);
119
+ }
120
+
121
+ .service-selector__content {
122
+ overflow-y: auto;
123
+ overflow-x: hidden;
124
+ scrollbar-width: thin;
125
+ scrollbar-color: #9ca3af #f3f4f6;
126
+ }
127
+
128
+ .service-selector__content::-webkit-scrollbar {
129
+ width: 6px;
130
+ }
131
+
132
+ .service-selector__content::-webkit-scrollbar-track {
133
+ background: #f3f4f6;
134
+ border-radius: 3px;
135
+ }
136
+
137
+ .service-selector__content::-webkit-scrollbar-thumb {
138
+ background: #9ca3af;
139
+ border-radius: 3px;
140
+ }
141
+
142
+ .service-selector__options-wrapper {
143
+ display: flex;
144
+ flex-direction: column;
145
+ padding: 4px;
146
+ }
147
+
148
+ /* Option */
149
+ .service-selector__option {
150
+ width: 100%;
151
+ padding: var(--service-selector-spacing-dropdown-item-padding-y, 12px) var(--service-selector-spacing-dropdown-item-padding-x, 24px);
152
+ display: flex;
153
+ align-items: center;
154
+ gap: var(--service-selector-spacing-dropdown-item-gap, 12px);
155
+ cursor: pointer;
156
+ transition: all 0.2s ease-in-out;
157
+ border: 0;
158
+ background-color: var(--service-selector-color-dropdown-item-background-default, transparent);
159
+ text-align: left;
160
+ border-radius: var(--service-selector-boder-radius-dropdown-item-default, 12px);
161
+ }
162
+
163
+ .service-selector__option:hover:not(:disabled):not(.service-selector__option--selected) {
164
+ background-color: var(--service-selector-color-dropdown-item-background-hover, #f5f5f5);
165
+ }
166
+
167
+ .service-selector__option:hover:not(:disabled):not(.service-selector__option--selected) .service-selector__option-icon,
168
+ .service-selector__option:hover:not(:disabled):not(.service-selector__option--selected) .service-selector__option-text {
169
+ color: var(--service-selector-color-dropdown-item-foreground-hover, #404040);
170
+ }
171
+
172
+ .service-selector__option--selected {
173
+ background-color: var(--service-selector-color-dropdown-item-background-active, #042c2f);
174
+ }
175
+
176
+ .service-selector__option--selected:hover {
177
+ background-color: var(--service-selector-color-dropdown-item-background-active, #042c2f);
178
+ }
179
+
180
+ .service-selector__option--selected .service-selector__option-icon,
181
+ .service-selector__option--selected .service-selector__option-text {
182
+ color: var(--service-selector-color-dropdown-item-foreground-active, #ffffff);
183
+ }
184
+
185
+ .service-selector__option--disabled {
186
+ opacity: 0.6;
187
+ cursor: not-allowed;
188
+ }
189
+
190
+ .service-selector__option--disabled:hover {
191
+ background-color: transparent;
192
+ }
193
+
194
+ .service-selector__option-icon {
195
+ flex-shrink: 0;
196
+ color: var(--service-selector-color-dropdown-item-foreground-default, #737373);
197
+ }
198
+
199
+ .service-selector__option-icon--selected {
200
+ color: var(--service-selector-color-dropdown-item-foreground-active, #ffffff);
201
+ }
202
+
203
+ .service-selector__option-text {
204
+ flex: 1;
205
+ color: var(--service-selector-color-dropdown-item-foreground-default, #737373);
206
+ }
207
+
208
+ .service-selector__option-badge {
209
+ padding: 2px 8px;
210
+ border-radius: var(--service-selector-boder-radius-dropdown-item-pill, 9999px);
211
+ font-size: 12px;
212
+ font-weight: 700;
213
+ margin-left: auto;
214
+ background-color: var(--color-brand-primary, #fe8839);
215
+ color: var(--color-text-inverted, #ffffff);
216
+ }
217
+
218
+ /* Responsive design */
219
+ @media (max-width: 640px) {
220
+ .service-selector__trigger {
221
+ padding: 10px 16px;
222
+ }
223
+
224
+ .service-selector__option {
225
+ padding: 10px 16px;
226
+ }
227
+ }
228
+
@@ -1,46 +0,0 @@
1
- import React from 'react';
2
-
3
- interface ChevronDownIconProps {
4
- size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
5
- className?: string;
6
- color?: string;
7
- }
8
-
9
- const ChevronDownIcon: React.FC<ChevronDownIconProps> = ({
10
- size = 'md',
11
- className = '',
12
- color
13
- }) => {
14
- const getSizeClasses = () => {
15
- switch (size) {
16
- case 'xs': return 'w-3 h-3';
17
- case 'sm': return 'w-4 h-4';
18
- case 'md': return 'w-5 h-5';
19
- case 'lg': return 'w-6 h-6';
20
- case 'xl': return 'w-8 h-8';
21
- default: return 'w-5 h-5';
22
- }
23
- };
24
-
25
- const sizeClasses = getSizeClasses();
26
- const colorClass = color ? `text-${color}` : 'text-current';
27
- const classes = `${sizeClasses} ${colorClass} ${className}`;
28
-
29
- return (
30
- <svg
31
- className={classes}
32
- viewBox="0 0 15 12"
33
- fill="none"
34
- xmlns="http://www.w3.org/2000/svg"
35
- >
36
- <path
37
- d="M12.4707 0.571533H2.07621C0.87817 0.571533 0.163582 1.90675 0.828137 2.90358L6.02536 10.6994C6.6191 11.59 7.92778 11.59 8.52151 10.6994L13.7187 2.90358C14.3833 1.90675 13.6687 0.571533 12.4707 0.571533Z"
38
- fill="currentColor"
39
- stroke="currentColor"
40
- strokeWidth="1.14286"
41
- />
42
- </svg>
43
- );
44
- };
45
-
46
- export default ChevronDownIcon;