@bit.rhplus/ui.f7.detail-item 0.0.2 → 0.0.3

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 (54) hide show
  1. package/components/Category.jsx +167 -0
  2. package/components/Contact.jsx +105 -0
  3. package/components/Custom.jsx +209 -4
  4. package/components/Download.jsx +32 -7
  5. package/components/Grid.jsx +230 -0
  6. package/components/InputNumber.jsx +17 -2
  7. package/components/InputText.jsx +17 -2
  8. package/components/List.jsx +305 -0
  9. package/components/Select.jsx +199 -0
  10. package/components/Switch.jsx +12 -1
  11. package/components/Text.jsx +36 -3
  12. package/components/index.jsx +6 -1
  13. package/dist/components/Category.d.ts +13 -0
  14. package/dist/components/Category.js +86 -0
  15. package/dist/components/Category.js.map +1 -0
  16. package/dist/components/Contact.d.ts +10 -0
  17. package/dist/components/Contact.js +60 -0
  18. package/dist/components/Contact.js.map +1 -0
  19. package/dist/components/Custom.d.ts +19 -2
  20. package/dist/components/Custom.js +114 -5
  21. package/dist/components/Custom.js.map +1 -1
  22. package/dist/components/Download.d.ts +5 -1
  23. package/dist/components/Download.js +19 -3
  24. package/dist/components/Download.js.map +1 -1
  25. package/dist/components/Grid.d.ts +19 -0
  26. package/dist/components/Grid.js +144 -0
  27. package/dist/components/Grid.js.map +1 -0
  28. package/dist/components/InputNumber.d.ts +3 -1
  29. package/dist/components/InputNumber.js +16 -2
  30. package/dist/components/InputNumber.js.map +1 -1
  31. package/dist/components/InputText.d.ts +3 -1
  32. package/dist/components/InputText.js +16 -2
  33. package/dist/components/InputText.js.map +1 -1
  34. package/dist/components/List.d.ts +20 -0
  35. package/dist/components/List.js +173 -0
  36. package/dist/components/List.js.map +1 -0
  37. package/dist/components/Select.d.ts +13 -0
  38. package/dist/components/Select.js +89 -0
  39. package/dist/components/Select.js.map +1 -0
  40. package/dist/components/Switch.d.ts +3 -1
  41. package/dist/components/Switch.js +11 -2
  42. package/dist/components/Switch.js.map +1 -1
  43. package/dist/components/Text.d.ts +8 -1
  44. package/dist/components/Text.js +25 -3
  45. package/dist/components/Text.js.map +1 -1
  46. package/dist/components/index.d.ts +5 -0
  47. package/dist/components/index.js +5 -0
  48. package/dist/components/index.js.map +1 -1
  49. package/dist/index.d.ts +3 -2
  50. package/dist/index.js +18 -11
  51. package/dist/index.js.map +1 -1
  52. package/index.jsx +109 -62
  53. package/package.json +6 -3
  54. /package/dist/{preview-1756999926762.js → preview-1757077532569.js} +0 -0
@@ -0,0 +1,167 @@
1
+ /* eslint-disable */
2
+ import React, { useState, useEffect } from 'react';
3
+ import {
4
+ Link,
5
+ Icon,
6
+ Popup,
7
+ Navbar,
8
+ NavLeft,
9
+ NavTitle,
10
+ NavTitleLarge,
11
+ NavRight,
12
+ Page,
13
+ Block,
14
+ List,
15
+ ListInput,
16
+ Button
17
+ } from 'framework7-react';
18
+ import { Tag } from 'lucide-react';
19
+ import CircleButton from '@bit.rhplus/ui.circle-button';
20
+
21
+ // Category komponenta s modální editací - zobrazuje výběr jedné kategorie jako avatar
22
+ export const Category = ({
23
+ children,
24
+ value,
25
+ onSave,
26
+ onChange, // Přidáno pro Form.Item kompatibilitu
27
+ title = 'Vyber',
28
+ placeholder = 'Vyber kategorii',
29
+ color = '#6887d3',
30
+ size = 16,
31
+ lucideIcon, // Lucide React ikona (např. Tag)
32
+ icon, // Jakákoliv React komponenta ikony
33
+ categories = [] // Array kategorií ve formátu [{ id, name, icon, color }]
34
+ }) => {
35
+ const [popupOpened, setPopupOpened] = useState(false);
36
+ const [selectedCategory, setSelectedCategory] = useState(value || null);
37
+
38
+ // Aktualizuj selectedCategory když se změní value prop (pro Form.Item kompatibilitu)
39
+ useEffect(() => {
40
+ if (value !== undefined) {
41
+ setSelectedCategory(value || null);
42
+ }
43
+ }, [value]);
44
+
45
+ const linkStyle = {
46
+ color,
47
+ cursor: 'pointer',
48
+ display: 'flex',
49
+ alignItems: 'center',
50
+ gap: '6px'
51
+ };
52
+
53
+ const handleCancel = () => {
54
+ setSelectedCategory(value || null);
55
+ setPopupOpened(false);
56
+ };
57
+
58
+ const selectCategory = (category) => {
59
+ // Automaticky ulož a zavři popup
60
+ setSelectedCategory(category);
61
+
62
+ // Preferuj onChange pro Form.Item kompatibilitu, jinak použij onSave
63
+ if (onChange) {
64
+ onChange(category);
65
+ } else if (onSave) {
66
+ onSave(category);
67
+ }
68
+
69
+ setPopupOpened(false);
70
+ };
71
+
72
+ // Určí jakou ikonu použít - priorita: icon > lucideIcon > výchozí Tag
73
+ const renderIcon = () => {
74
+ if (icon) {
75
+ return React.cloneElement(icon, { size, color, ...icon.props });
76
+ }
77
+ if (lucideIcon) {
78
+ const LucideIcon = lucideIcon;
79
+ return <LucideIcon size={size} color={color} />;
80
+ }
81
+ return <Tag size={size} color={color} />;
82
+ };
83
+
84
+ // Zobrazí název vybrané kategorie nebo placeholder
85
+ const renderDisplayText = () => {
86
+ if (selectedCategory && selectedCategory.name) {
87
+ return selectedCategory.name;
88
+ }
89
+ return placeholder;
90
+ };
91
+
92
+ return (
93
+ <>
94
+ <Link onClick={() => setPopupOpened(true)} className="link" style={linkStyle}>
95
+ {renderIcon()}
96
+ {renderDisplayText()}
97
+ </Link>
98
+
99
+ <Popup
100
+ opened={popupOpened}
101
+ onPopupClosed={() => setPopupOpened(false)}
102
+ animate
103
+ backdrop
104
+ push={false}
105
+ className="f7-parallax category-popup"
106
+ style={{
107
+ '--f7-popup-tablet-width': '90vw',
108
+ '--f7-popup-tablet-height': '90vh'
109
+ }}
110
+ >
111
+ <div className="view view-init">
112
+ <div className="page page-with-navbar-large">
113
+ <Navbar large className="navbar-transparent">
114
+ <NavLeft>
115
+ <Link onClick={handleCancel}>
116
+ <Icon f7="arrow_left" style={{ fontWeight: 'bold' }} />
117
+ </Link>
118
+ </NavLeft>
119
+ <NavTitle>{title}</NavTitle>
120
+ <NavTitleLarge>{title}</NavTitleLarge>
121
+ </Navbar>
122
+
123
+ <div className="page-content">
124
+ {/* Dostupné kategorie */}
125
+ <Block>
126
+ <div style={{
127
+ display: 'grid',
128
+ gridTemplateColumns: 'repeat(auto-fit, minmax(80px, 1fr))',
129
+ gap: '15px',
130
+ marginTop: '15px',
131
+ padding: '10px 5px'
132
+ }}>
133
+ {categories.map((category) => {
134
+ const isSelected = selectedCategory && selectedCategory.id === category.id;
135
+ return (
136
+ <div key={category.id} style={{
137
+ display: 'flex',
138
+ justifyContent: 'center',
139
+ minWidth: '80px'
140
+ }}>
141
+ <CircleButton
142
+ icon={category.icon || 'tag'}
143
+ name={category.name}
144
+ bgColor={isSelected ? '#28a745' : (category.color || '#6887d3')}
145
+ iconColor="white"
146
+ textColor="#333"
147
+ onClick={() => selectCategory(category)}
148
+ style={{
149
+ opacity: isSelected ? 1 : 0.7,
150
+ transform: isSelected ? 'scale(1.05)' : 'scale(1)',
151
+ transition: 'all 0.2s ease',
152
+ width: '100%',
153
+ maxWidth: '100px'
154
+ }}
155
+ />
156
+ </div>
157
+ );
158
+ })}
159
+ </div>
160
+ </Block>
161
+ </div>
162
+ </div>
163
+ </div>
164
+ </Popup>
165
+ </>
166
+ );
167
+ };
@@ -0,0 +1,105 @@
1
+ /* eslint-disable */
2
+ import React from 'react';
3
+ import { ListInput } from 'framework7-react';
4
+ import { UserCheck } from 'lucide-react';
5
+ import { Custom } from './Custom';
6
+
7
+ // Renderer pro Contact formulář
8
+ const ContactRenderer = ({ fields, formData, onChange }) => {
9
+ const handleInputChange = (fieldKey, event) => {
10
+ const newValue = event.target.value;
11
+ onChange(fieldKey, newValue);
12
+ };
13
+
14
+ return (
15
+ <>
16
+ {fields.map((field, index) => (
17
+ <ListInput
18
+ key={field.key || index}
19
+ type={field.type || 'text'}
20
+ label={field.label}
21
+ placeholder={field.placeholder}
22
+ value={formData[field.key] || ''}
23
+ onInput={(e) => handleInputChange(field.key, e)}
24
+ clearButton
25
+ style={{ fontSize: '18px' }}
26
+ // Speciální styling pro různé typy polí
27
+ {...(field.type === 'email' && {
28
+ inputMode: 'email',
29
+ autoComplete: 'email'
30
+ })}
31
+ {...(field.type === 'tel' && {
32
+ inputMode: 'tel',
33
+ autoComplete: 'tel'
34
+ })}
35
+ />
36
+ ))}
37
+ </>
38
+ );
39
+ };
40
+
41
+ // Externí PM komponenta s předdefinovanými poli
42
+ export const Contact = ({
43
+ value,
44
+ onSave,
45
+ onChange,
46
+ title = 'Kontakt',
47
+ placeholder = 'Zadej kontakt',
48
+ color = '#6887d3',
49
+ size = 16,
50
+ ...restProps
51
+ }) => {
52
+ // Předdefinovaná pole pro Externí PM
53
+ const pmFields = [
54
+ {
55
+ key: 'firstName',
56
+ label: 'Jméno',
57
+ type: 'text',
58
+ placeholder: 'Zadejte jméno'
59
+ },
60
+ {
61
+ key: 'lastName',
62
+ label: 'Příjmení',
63
+ type: 'text',
64
+ placeholder: 'Zadejte příjmení'
65
+ },
66
+ {
67
+ key: 'email',
68
+ label: 'Email',
69
+ type: 'email',
70
+ placeholder: 'Zadejte email'
71
+ },
72
+ {
73
+ key: 'phone',
74
+ label: 'Telefon',
75
+ type: 'tel',
76
+ placeholder: 'Zadejte telefon'
77
+ },
78
+ {
79
+ key: 'position',
80
+ label: 'Pozice',
81
+ type: 'text',
82
+ placeholder: 'Zadejte pozici'
83
+ }
84
+ ];
85
+
86
+ // Pole pro zobrazení v linku - jméno, příjmení a pozice
87
+ const displayFields = ['firstName', 'lastName', 'position'];
88
+
89
+ return (
90
+ <Custom
91
+ value={value}
92
+ onSave={onSave}
93
+ onChange={onChange}
94
+ title={title}
95
+ placeholder={placeholder}
96
+ color={color}
97
+ size={size}
98
+ lucideIcon={UserCheck}
99
+ FormRenderer={ContactRenderer}
100
+ fields={pmFields}
101
+ displayFields={displayFields}
102
+ {...restProps}
103
+ />
104
+ );
105
+ };
@@ -1,6 +1,211 @@
1
1
  /* eslint-disable */
2
- import React from 'react';
3
- // Custom wrapper pro vlastní obsah
4
- export const Custom = ({ children }) => {
5
- return <>{children}</>;
2
+ import React, { useState, useEffect } from 'react';
3
+ import {
4
+ Link,
5
+ Icon,
6
+ Popup,
7
+ Navbar,
8
+ NavLeft,
9
+ NavTitle,
10
+ NavTitleLarge,
11
+ Page,
12
+ Block,
13
+ List,
14
+ ListInput
15
+ } from 'framework7-react';
16
+ import { User } from 'lucide-react';
17
+ import SaveButton from '@bit.rhplus/ui.f7.save-button';
18
+
19
+ // External Contact komponenta - formulář pro zadání externího kontaktu
20
+ export const Custom = ({
21
+ value,
22
+ onSave,
23
+ onChange,
24
+ title = 'Externí kontakt',
25
+ placeholder = 'Zadejte kontakt',
26
+ color = '#6887d3',
27
+ size = 16,
28
+ lucideIcon,
29
+ icon,
30
+ FormRenderer, // Custom komponenta pro renderování formuláře
31
+ fields = [ // Výchozí definice polí
32
+ { key: 'firstName', label: 'Jméno', type: 'text', placeholder: 'Zadejte jméno' },
33
+ { key: 'lastName', label: 'Příjmení', type: 'text', placeholder: 'Zadejte příjmení' },
34
+ { key: 'phone', label: 'Telefon', type: 'tel', placeholder: 'Zadejte telefon' },
35
+ { key: 'email', label: 'Email', type: 'email', placeholder: 'Zadejte email' },
36
+ { key: 'position', label: 'Pozice', type: 'text', placeholder: 'Zadejte pozici' }
37
+ ],
38
+ displayFields = ['firstName', 'lastName', 'position'], // Pole pro zobrazení v linku
39
+ ...restProps
40
+ }) => {
41
+ const [popupOpened, setPopupOpened] = useState(false);
42
+
43
+ // Dynamicky inicializuj formData na základě fields
44
+ const getInitialFormData = () => {
45
+ const initialData = {};
46
+ fields.forEach(field => {
47
+ initialData[field.key] = '';
48
+ });
49
+ return initialData;
50
+ };
51
+
52
+ const [formData, setFormData] = useState(getInitialFormData());
53
+
54
+ // Aktualizuj formData když se změní value prop nebo fields (ale ne když je popup otevřený)
55
+ useEffect(() => {
56
+ if (popupOpened) return; // Neaktualizuj formData když je popup otevřený
57
+
58
+ const newFormData = {};
59
+ fields.forEach(field => {
60
+ newFormData[field.key] = '';
61
+ });
62
+
63
+ if (value && typeof value === 'object') {
64
+ fields.forEach(field => {
65
+ newFormData[field.key] = value[field.key] || '';
66
+ });
67
+ }
68
+
69
+ setFormData(newFormData);
70
+ }, [value, fields, popupOpened]);
71
+
72
+ const linkStyle = {
73
+ color,
74
+ cursor: 'pointer',
75
+ display: 'flex',
76
+ alignItems: 'center',
77
+ gap: '6px'
78
+ };
79
+
80
+ const handleSave = () => {
81
+ const result = { ...formData };
82
+
83
+ if (onChange) {
84
+ onChange(result);
85
+ } else if (onSave) {
86
+ onSave(result);
87
+ }
88
+ setPopupOpened(false);
89
+ };
90
+
91
+ const handleCancel = () => {
92
+ const newFormData = {};
93
+ fields.forEach(field => {
94
+ newFormData[field.key] = '';
95
+ });
96
+
97
+ if (value && typeof value === 'object') {
98
+ fields.forEach(field => {
99
+ newFormData[field.key] = value[field.key] || '';
100
+ });
101
+ }
102
+
103
+ setFormData(newFormData);
104
+ setPopupOpened(false);
105
+ };
106
+
107
+ const handleInputChange = (field, newValue) => {
108
+ setFormData(prev => ({
109
+ ...prev,
110
+ [field]: newValue
111
+ }));
112
+ };
113
+
114
+ const handleOpenPopup = () => {
115
+ // Před otevřením popupu nastav aktuální hodnoty do formData
116
+ const newFormData = {};
117
+ fields.forEach(field => {
118
+ newFormData[field.key] = '';
119
+ });
120
+
121
+ if (value && typeof value === 'object') {
122
+ fields.forEach(field => {
123
+ newFormData[field.key] = value[field.key] || '';
124
+ });
125
+ }
126
+
127
+ setFormData(newFormData);
128
+ setPopupOpened(true);
129
+ };
130
+
131
+ // Určí jakou ikonu použít - priorita: icon > lucideIcon > výchozí User
132
+ const renderIcon = () => {
133
+ if (icon) {
134
+ return React.cloneElement(icon, { size, color, ...icon.props });
135
+ }
136
+ if (lucideIcon) {
137
+ const LucideIcon = lucideIcon;
138
+ return <LucideIcon size={size} color={color} />;
139
+ }
140
+ return <User size={size} color={color} />;
141
+ };
142
+
143
+ // Zobrazí text v linku podle vyplněných dat
144
+ const getDisplayText = () => {
145
+ if (value && typeof value === 'object') {
146
+ const displayValues = displayFields
147
+ .map(fieldKey => value[fieldKey])
148
+ .filter(Boolean);
149
+
150
+ if (displayValues.length > 0) {
151
+ return displayValues.join(' - ');
152
+ }
153
+ }
154
+ return placeholder;
155
+ };
156
+
157
+ return (
158
+ <div {...restProps}>
159
+ <Link onClick={handleOpenPopup} className="link" style={linkStyle}>
160
+ {renderIcon()}
161
+ {getDisplayText()}
162
+ </Link>
163
+
164
+ <Popup
165
+ opened={popupOpened}
166
+ onPopupClosed={() => setPopupOpened(false)}
167
+ animate
168
+ backdrop
169
+ className="f7-parallax"
170
+ >
171
+ <Page>
172
+ <Navbar large>
173
+ <NavLeft>
174
+ <Link onClick={handleCancel}>
175
+ <Icon f7="arrow_left" style={{ fontWeight: 'bold' }} />
176
+ </Link>
177
+ </NavLeft>
178
+ <NavTitle>{title}</NavTitle>
179
+ <NavTitleLarge>{title}</NavTitleLarge>
180
+ </Navbar>
181
+
182
+ <Block style={{ marginTop: '20px' }}>
183
+ <List>
184
+ {FormRenderer ? (
185
+ <FormRenderer
186
+ fields={fields}
187
+ formData={formData}
188
+ onChange={handleInputChange}
189
+ />
190
+ ) : (
191
+ fields.map((field, index) => (
192
+ <ListInput
193
+ key={field.key || index}
194
+ type={field.type || 'text'}
195
+ label={field.label}
196
+ placeholder={field.placeholder}
197
+ value={formData[field.key]}
198
+ onInput={(e) => handleInputChange(field.key, e.target.value)}
199
+ clearButton
200
+ style={{ fontSize: '18px' }}
201
+ />
202
+ ))
203
+ )}
204
+ </List>
205
+ </Block>
206
+ <SaveButton onClick={handleSave} variant="black" />
207
+ </Page>
208
+ </Popup>
209
+ </div>
210
+ );
6
211
  };
@@ -1,9 +1,22 @@
1
1
  /* eslint-disable */
2
2
  import React, { useState } from 'react';
3
3
  import { Link, Icon, Preloader } from 'framework7-react';
4
+ import { Download as DownloadIcon } from 'lucide-react';
4
5
 
5
6
  // Download komponenta s loading stavem
6
- export const Download = ({ children, href, onClick, color = '#6887d3', size = 16 }) => {
7
+ export const Download = ({
8
+ children,
9
+ href,
10
+ onClick,
11
+ color = '#6887d3',
12
+ size = 16,
13
+ value,
14
+ lucideIcon, // Lucide React ikona
15
+ icon, // Jakákoliv React komponenta ikony
16
+ ...restProps
17
+ }) => {
18
+ // Pro Form.Item kompatibilitu - zobraz value pokud je poskytnut, jinak children
19
+ const displayText = value !== undefined ? value : children;
7
20
  const [isLoading, setIsLoading] = useState(false);
8
21
 
9
22
  const handleClick = async (e) => {
@@ -24,28 +37,40 @@ export const Download = ({ children, href, onClick, color = '#6887d3', size = 16
24
37
  alignItems: 'center',
25
38
  gap: '6px'
26
39
  };
40
+
41
+ // Určí jakou ikonu použít - priorita: icon > lucideIcon > výchozí DownloadIcon
42
+ const renderIcon = () => {
43
+ if (icon) {
44
+ return React.cloneElement(icon, { size, color, ...icon.props });
45
+ }
46
+ if (lucideIcon) {
47
+ const LucideIcon = lucideIcon;
48
+ return <LucideIcon size={size} color={color} />;
49
+ }
50
+ return <DownloadIcon size={size} color={color} />;
51
+ };
27
52
 
28
53
  if (href) {
29
54
  return (
30
- <Link href={href} className="link" style={linkStyle}>
55
+ <Link href={href} className="link" style={linkStyle} {...restProps}>
31
56
  {isLoading ? (
32
57
  <Preloader size={size} />
33
58
  ) : (
34
- <Icon f7="arrow_down_circle" size={size} />
59
+ renderIcon()
35
60
  )}
36
- {children}
61
+ {displayText}
37
62
  </Link>
38
63
  );
39
64
  }
40
65
 
41
66
  return (
42
- <Link onClick={handleClick} className="link" style={linkStyle}>
67
+ <Link onClick={handleClick} className="link" style={linkStyle} {...restProps}>
43
68
  {isLoading ? (
44
69
  <Preloader size={size} />
45
70
  ) : (
46
- <Icon f7="arrow_down_circle" size={size} />
71
+ renderIcon()
47
72
  )}
48
- {children}
73
+ {displayText}
49
74
  </Link>
50
75
  );
51
76
  };