naria-ui 0.1.36 → 0.1.38

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.
@@ -0,0 +1,16 @@
1
+ import React, {FC} from "react";
2
+
3
+ export interface props extends React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
4
+ type?: "button" | "submit";
5
+ value: string;
6
+ }
7
+ export const Button: FC<props> = ({type = "button", value}) => {
8
+
9
+ return (
10
+ <button type={type}>
11
+ {value}
12
+ </button>
13
+ );
14
+ };
15
+
16
+
@@ -0,0 +1 @@
1
+ export { Button } from './Button';
@@ -0,0 +1,59 @@
1
+ import {FC} from "react";
2
+
3
+ export interface props {
4
+ type?: "password" | "text";
5
+ wrapperClass?: string;
6
+ labelClass?: string;
7
+ inputClass?: string;
8
+ errorClass?: string;
9
+ placeholder: string;
10
+ label: string;
11
+ hasError?: string | null;
12
+ register?: any;
13
+ name?: string;
14
+ isDisabled?: boolean;
15
+ autocomplete?: boolean;
16
+ }
17
+
18
+ export const Input: FC<props> = ({
19
+ type = "text",
20
+ placeholder,
21
+ wrapperClass = "",
22
+ labelClass = "",
23
+ inputClass = "",
24
+ errorClass = "",
25
+ label,
26
+ hasError,
27
+ register, name,
28
+ isDisabled = false,
29
+ autocomplete = false,
30
+ ...otherProps
31
+ }) => {
32
+
33
+ return (
34
+ <div className={wrapperClass}>
35
+ <label
36
+ htmlFor={name}
37
+ className={`${labelClass} ${hasError && "error"}`}>
38
+ {label}
39
+ <input
40
+ disabled={isDisabled}
41
+ autoComplete={autocomplete ? "on" : "off"}
42
+ id={name}
43
+ {...register}
44
+ {...otherProps}
45
+ type={type}
46
+ name={name}
47
+ className={`${inputClass} ${hasError && "error"} ${errorClass}`}
48
+ placeholder={placeholder}
49
+ />
50
+ </label>
51
+ {
52
+ hasError &&
53
+ <p className={errorClass}>{hasError}</p>
54
+ }
55
+ </div>
56
+ );
57
+ };
58
+
59
+
@@ -0,0 +1 @@
1
+ export { Input } from './Input';
@@ -0,0 +1,380 @@
1
+ import {FC, useEffect, useRef, useState} from "react";
2
+ import AngleDown from "../../../assets/icons/angle-down.svg?react";
3
+ import Close from '../../../assets/icons/close.svg?react';
4
+ import Search from '../../../assets/icons/search.svg?react';
5
+ import {useWidth} from "../../../hooks/use-width";
6
+ import Loading from "../../../shared/loading/Loading";
7
+ import './select.scss';
8
+ import {addNavigation, onHashChanges, removeNavigation} from "../../utils/navigator";
9
+ import useClickOutside from "../../../hooks/click-outside";
10
+
11
+ interface Pagination {
12
+ page?: number;
13
+ pageLabel?: string;
14
+ size?: number;
15
+ sizeLabel?: string;
16
+ }
17
+
18
+ interface SDS extends Pagination {
19
+ isLoading?: boolean;
20
+ }
21
+
22
+ export interface props {
23
+ options?: any[];
24
+ label?: string;
25
+ title: string;
26
+ value?: string;
27
+ api?: string;
28
+ hasError?: string | null;
29
+ selected?: any;
30
+ placeholder?: string;
31
+ disabled?: boolean;
32
+ pagination?: Pagination;
33
+ optionFilterLabel?: string;
34
+ hasSearch?: boolean;
35
+ onSelectChange?: any;
36
+ }
37
+
38
+ export const Select: FC<props> = ({
39
+ options,
40
+ label,
41
+ hasError,
42
+ title,
43
+ value,
44
+ api,
45
+ selected,
46
+ placeholder,
47
+ disabled = false,
48
+ pagination,
49
+ optionFilterLabel,
50
+ hasSearch = false,
51
+ onSelectChange
52
+ }) => {
53
+ let isSubscribed = true;
54
+ const getDeviceWidth = useWidth();
55
+
56
+ const isHashChanged = onHashChanges('#select');
57
+ const [isShow, setIsShow] = useState(false);
58
+ const [isLoading, setIsLoading] = useState(true);
59
+ const [localSelected, setLocalSelected] = useState<string | null>(null);
60
+ const [localOptions, setLocalOptions] = useState(null);
61
+ const [searchTerm, setSearchTerm] = useState("");
62
+ const [localPagination, setLocalPagination] = useState<SDS>({
63
+ page: 1,
64
+ pageLabel: 'page',
65
+ size: 20,
66
+ sizeLabel: 'size',
67
+ isLoading: false
68
+ });
69
+ const localApi = useRef<string | undefined>(undefined);
70
+ const wrapperRef = useRef(undefined);
71
+ const handlerRef = useRef(undefined);
72
+
73
+ const getData = async () => {
74
+ if (localApi?.current) {
75
+ try {
76
+ const response = await fetch(localApi?.current);
77
+ if (!response.ok) {
78
+ throw new Error(`Response status: ${response.status}`);
79
+ }
80
+ return await response.json();
81
+ } catch (error) {
82
+ console.error(error.message);
83
+ }
84
+ }
85
+
86
+ }
87
+
88
+ useEffect(() => {
89
+ return () => {
90
+ isSubscribed = false;
91
+ };
92
+ }, [])
93
+ useEffect(() => {
94
+ if (api?.length) {
95
+ localApi.current = api;
96
+ if (localApi.current.includes(pagination?.pageLabel || 'page')) {
97
+ const url = new URL(api);
98
+ url.searchParams.set(pagination?.pageLabel || 'page', (pagination?.page || localPagination.page).toString());
99
+ url.searchParams.set(pagination?.sizeLabel || 'size', (pagination?.size || localPagination.size).toString());
100
+ localApi.current = url.href;
101
+ }
102
+ setIsLoading(true);
103
+ getData().then((res) => {
104
+ if (isSubscribed) {
105
+ setIsLoading(false);
106
+ setLocalOptions(res);
107
+ }
108
+ })
109
+ }
110
+ }, [api, pagination]);
111
+
112
+ useEffect(() => {
113
+ if (localPagination.isLoading) {
114
+ if (localApi.current.includes(pagination?.pageLabel || 'page')) {
115
+ const url = new URL(localApi.current);
116
+ url.searchParams.set(pagination?.pageLabel || 'page', (localPagination.page).toString());
117
+ localApi.current = url.href;
118
+ }
119
+ getData().then((res) => {
120
+ if (isSubscribed) {
121
+ setIsLoading(false);
122
+ setLocalPagination({
123
+ ...localPagination,
124
+ isLoading: false
125
+ })
126
+ setLocalOptions([
127
+ ...localOptions,
128
+ ...res
129
+ ]);
130
+ }
131
+ })
132
+ }
133
+ }, [localPagination]);
134
+ useEffect(() => {
135
+ if (getDeviceWidth < 768) {
136
+ if (isShow) {
137
+ addNavigation('select');
138
+ document.body.style.overflow = 'hidden';
139
+ handlerRef.current?.focus();
140
+ } else {
141
+ if (window.location.hash && !document.referrer.includes('#')) {
142
+ removeNavigation();
143
+ }
144
+ document.body.style.overflow = 'auto';
145
+ }
146
+ }
147
+ }, [isShow]);
148
+
149
+ useEffect(() => {
150
+ if (selected && options?.length) {
151
+ if (options?.find(item => item[label] === selected)) {
152
+ setLocalSelected(options?.find(item => item[label] === selected));
153
+ } else {
154
+ setLocalSelected(selected);
155
+ }
156
+ }
157
+ }, [selected]);
158
+
159
+ useEffect(() => {
160
+ if (localSelected) {
161
+ if (hasSearch) {
162
+ if (value?.length && localOptions?.find(item => item[label] === localSelected[label])) {
163
+ setSearchTerm(localOptions?.find(item => item[label] === localSelected[label])[value] || '');
164
+ } else {
165
+ setSearchTerm(localSelected);
166
+ }
167
+ }
168
+ }
169
+ }, [localSelected]);
170
+
171
+ useEffect(() => {
172
+ if (isHashChanged) {
173
+ setIsShow(false)
174
+ }
175
+ }, [isHashChanged])
176
+
177
+
178
+ useEffect(() => {
179
+ if (options?.length) {
180
+ setLocalOptions(options)
181
+ }
182
+ }, [options]);
183
+
184
+ const onToggle = () => {
185
+ if (hasSearch) {
186
+ setLocalOptions(options)
187
+ }
188
+ setIsShow(prevState => !prevState);
189
+ }
190
+ const onClose = () => {
191
+ if (hasSearch) {
192
+ setLocalOptions(options)
193
+ if (typeof localSelected === "string") {
194
+ setSearchTerm(localSelected);
195
+ } else {
196
+ setSearchTerm(localSelected ? localSelected[value] : "")
197
+ }
198
+ }
199
+ setIsShow(false);
200
+ }
201
+ const onSelect = (item) => {
202
+ if (hasSearch) {
203
+ if (value?.length) {
204
+ setSearchTerm(item[value]);
205
+ } else {
206
+ setSearchTerm(item);
207
+ }
208
+ }
209
+ setLocalSelected(item);
210
+ onToggle();
211
+ if (onSelectChange) {
212
+ onSelectChange(item);
213
+ }
214
+ }
215
+
216
+ const getActiveClass = (item) => {
217
+ if (!localSelected) {
218
+ return "";
219
+ }
220
+ if (label?.length && item[label] === localSelected[label]) {
221
+ return "bg-grey-100"
222
+ }
223
+ if (item === localSelected) {
224
+ return "bg-grey-100"
225
+ }
226
+ }
227
+ const onScroll = (e) => {
228
+ if (localOptions?.length && !localPagination.isLoading) {
229
+ const bottom = e.target.offsetHeight + e.target.scrollTop >= e.target.scrollHeight - 100;
230
+
231
+ if (bottom) {
232
+ setLocalPagination({
233
+ page: localPagination.page + 1,
234
+ isLoading: true
235
+ })
236
+ }
237
+ }
238
+
239
+ }
240
+ const onSearch = (e) => {
241
+ const tempList = e?.target?.value?.length ? options.filter(val => typeof val === "object" ? val[value].includes(e?.target?.value) : val.includes(e?.target?.value)) : options
242
+ setLocalOptions(tempList)
243
+ setSearchTerm(e?.target?.value)
244
+ if (!isShow) {
245
+ setIsShow(true)
246
+ }
247
+ }
248
+ useClickOutside(wrapperRef, handlerRef, onClose);
249
+ return (
250
+ <div className={`nariaSelect ${disabled ? 'nariaSelect-disabled' : ''}`}>
251
+ <label
252
+ className={`cursor-pointer
253
+ ${hasError && "!text-danger-100"}`}>
254
+ <span className={``}>{title}</span>
255
+ {
256
+ hasSearch ? (
257
+ <div className="nariaSearchInput">
258
+ <input ref={handlerRef}
259
+ placeholder={placeholder?.length ? placeholder : "Select"}
260
+ className={`${localSelected ? "text-dark-100" : "text-grey-300"}
261
+ ${hasError && "!border-danger-100 focus:border-danger-100 outline-danger-100"}`}
262
+ value={searchTerm}
263
+ disabled={disabled} type="text" onClick={onToggle} onChange={onSearch}/>
264
+ {
265
+ isShow ? (
266
+ <Search
267
+ className="nariaSearchIcon"/>
268
+ ) : (
269
+ <AngleDown
270
+ className={`nariaArrowIcon ${isShow ? "nariaArrowIcon-rotate-180" : ""}`}/>
271
+ )
272
+ }
273
+
274
+
275
+ </div>
276
+ ) : (
277
+ <button type="button"
278
+ ref={handlerRef}
279
+ disabled={disabled}
280
+ className={`nariaHandler ${localSelected ? "text-dark-100" : "text-grey-300"}
281
+ ${hasError && "!border-danger-100 focus:border-danger-100 outline-danger-100"}`}
282
+ onClick={onToggle}>
283
+ {
284
+ localSelected ? (
285
+ value?.length ? localSelected[value] : localSelected
286
+ ) : (placeholder?.length ? placeholder : "Select")
287
+ } <AngleDown
288
+ className={`nariaArrowIcon ${isShow ? "nariaArrowIcon-rotate-180" : ""}`}/>
289
+ </button>
290
+ )
291
+ }
292
+
293
+ </label>
294
+
295
+ {
296
+ isShow ? (
297
+ <div
298
+ className={`nariaListWrapper ${getDeviceWidth < 768 ? "nariaListWrapper-mobile" : ""}`}
299
+ ref={wrapperRef}>
300
+ {
301
+ hasSearch && getDeviceWidth < 768 ? (
302
+ <div className="nariaSearchInput">
303
+ <input ref={handlerRef}
304
+ placeholder={placeholder?.length ? placeholder : "Select"}
305
+ className={`${localSelected ? "text-dark-100" : "text-grey-300"}
306
+ ${hasError && "!border-danger-100 focus:border-danger-100 outline-danger-100"}`}
307
+ value={searchTerm}
308
+ disabled={disabled} type="text" onChange={onSearch}/>
309
+ <Search
310
+ className="nariaSearchIcon"/>
311
+
312
+
313
+ </div>
314
+ ) : undefined
315
+ }
316
+ <div
317
+ className={`nariaList ${getDeviceWidth < 768 ? "nariaList-mobile" : `nariaList-desktop`}`}
318
+ onScroll={onScroll}>
319
+ {
320
+ api && isLoading ? (
321
+ <div className="nariaLoadingWrapper">
322
+ <Loading/>
323
+ </div>
324
+ ) : (
325
+ <>
326
+ {
327
+ getDeviceWidth < 768 ? (
328
+ <div className="sticky top-0 text-left">
329
+ <button className="p-3" onClick={onClose} disabled={disabled}>
330
+ <Close className="w-6"/>
331
+ </button>
332
+ </div>
333
+ ) : undefined
334
+ }
335
+ {
336
+ localOptions?.length ? (
337
+ <>
338
+ {
339
+ localOptions?.map((item, index) => {
340
+ return (
341
+ <button type="button" onClick={() => onSelect(item)}
342
+ disabled={disabled}
343
+ key={index.toString()}
344
+ className={`text-right py-2.5 px-4 text-base hover:bg-grey-100 rounded-lg ${getActiveClass(item)}`}>
345
+ {value?.length ? item[value] : item}
346
+ </button>
347
+ )
348
+ })
349
+ }
350
+ </>
351
+ ) : (
352
+ <div>
353
+ No Data
354
+ </div>
355
+ )
356
+ }
357
+
358
+
359
+ {
360
+ localPagination.isLoading ? (
361
+ <div className="nariaLoadingMoreWrapper">
362
+ <Loading/>
363
+ </div>
364
+ ) : undefined
365
+ }
366
+ </>
367
+ )
368
+ }
369
+ </div>
370
+ </div>
371
+ ) : undefined
372
+ }
373
+ {
374
+ hasError &&
375
+ <p className="text-xs mt-1 text-danger-100">{hasError}</p>
376
+ }
377
+
378
+ </div>
379
+ );
380
+ };
@@ -0,0 +1 @@
1
+ export { Select } from './Select';
@@ -0,0 +1,132 @@
1
+
2
+ .nariaSelect {
3
+ position: relative;
4
+
5
+ .nariaListWrapper {
6
+ overflow: hidden;
7
+
8
+ &.nariaListWrapper-mobile {
9
+ position: fixed;
10
+ top: 0;
11
+ right: 0;
12
+ height: 100%;
13
+ width: 100%;
14
+ z-index: 97;
15
+ }
16
+ }
17
+
18
+ .nariaList {
19
+ display: flex;
20
+ flex-direction: column;
21
+ width: 100%;
22
+ z-index: 99;
23
+ position: relative;
24
+ overflow: auto;
25
+
26
+ &.nariaList-mobile {
27
+ position: relative;
28
+ height: 100%;
29
+ }
30
+
31
+ &.nariaList-desktop {
32
+ position: absolute;
33
+ animation: fadeInTranslateY 0.3s ease-out forwards;
34
+ max-height: 100px;
35
+ }
36
+
37
+ .nariaLoadingWrapper {
38
+ padding: 24px 0;
39
+ display: flex;
40
+ justify-content: center;
41
+
42
+ }
43
+
44
+ .nariaLoadingMoreWrapper {
45
+ position: sticky;
46
+ bottom: 0;
47
+ text-align: center;
48
+ width: 100%;
49
+
50
+ .nariaLoading {
51
+ width: 20px;
52
+ }
53
+ }
54
+ }
55
+
56
+ .nariaSearchInput {
57
+ position: relative;
58
+ display: flex;
59
+
60
+ input {
61
+ width: 100%;
62
+ padding-right: 20px;
63
+ }
64
+
65
+ .nariaArrowIcon, .nariaSearchIcon {
66
+ position: absolute;
67
+ bottom: 0;
68
+ margin-top: auto;
69
+ margin-bottom: auto;
70
+ top: 0;
71
+ right: 6px;
72
+ width: 12px;
73
+ height: 12px;
74
+ }
75
+
76
+ }
77
+
78
+ .nariaHandler {
79
+ position: relative;
80
+ display: flex;
81
+ gap: 4px;
82
+ width: 100%;
83
+ align-items: center;
84
+ justify-content: space-between;
85
+
86
+ .nariaArrowIcon {
87
+ width: 12px;
88
+ height: 12px;
89
+ transition: transform 0.4s;
90
+
91
+ -webkit-transform: rotate(0deg);
92
+ -moz-transform: rotate(0deg);
93
+ -o-transform: rotate(0deg);
94
+ -ms-transform: rotate(0deg);
95
+ transform: rotate(0deg);
96
+
97
+ &.nariaArrowIcon-rotate-180 {
98
+ -webkit-transform: rotate(180deg);
99
+ -moz-transform: rotate(180deg);
100
+ -o-transform: rotate(180deg);
101
+ -ms-transform: rotate(180deg);
102
+ transform: rotate(180deg);
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ html[dir="rtl"] {
109
+ .nariaSelect {
110
+ .nariaSearchInput {
111
+ input {
112
+ padding-left: 20px;
113
+ padding-right: 0;
114
+ }
115
+ .nariaArrowIcon, .nariaSearchIcon {
116
+ left: 6px;
117
+ right: auto;
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ @keyframes fadeInTranslateY {
124
+ from {
125
+ opacity: 0;
126
+ transform: translateY(10px);
127
+ }
128
+ to {
129
+ opacity: 1;
130
+ transform: translateY(0);
131
+ }
132
+ }
@@ -0,0 +1,3 @@
1
+ export * from './Button'
2
+ export * from './Input'
3
+ export * from './Select'
package/lib/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './components';
2
+ export * from './utils';
@@ -0,0 +1,4 @@
1
+ export function capitalize(str: string): string {
2
+ return str.charAt(0).toUpperCase() + str.slice(1);
3
+ }
4
+
@@ -0,0 +1,2 @@
1
+ export * from './capitalize';
2
+
@@ -0,0 +1,29 @@
1
+ import {useEffect, useState} from "react";
2
+
3
+ export const removeNavigation = () => {
4
+ const newUrl = `${window.location.pathname}${window.location.search}`;
5
+ window.history.replaceState(null, '', newUrl);
6
+ }
7
+
8
+ export const addNavigation = (state: string) => {
9
+ window.location.hash = state;
10
+ }
11
+
12
+
13
+ export const onHashChanges = (state: string) => {
14
+ const [isHashChanged, setIsHashChanged] = useState(false)
15
+ useEffect(() => {
16
+ const handleHashChange = (e) => {
17
+ if(window.location.hash !== state) {
18
+ setIsHashChanged(true);
19
+ } else {
20
+ setIsHashChanged(false);
21
+ }
22
+ };
23
+ window.addEventListener('hashchange', handleHashChange);
24
+ return () => {
25
+ window.removeEventListener('hashchange', handleHashChange);
26
+ };
27
+ }, [])
28
+ return isHashChanged
29
+ };
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "Mohammad Mehdi Mohammadi",
6
6
  "url": ""
7
7
  },
8
- "version": "0.1.36",
8
+ "version": "0.1.38",
9
9
  "type": "module",
10
10
  "main": "dist/naria-ui.cjs.js",
11
11
  "module": "dist/naria-ui.es.js",
@@ -15,11 +15,11 @@
15
15
  "types": "./dist/index.d.ts",
16
16
  "import": "./dist/naria-ui.es.js",
17
17
  "require": "./dist/naria-ui.cjs.js"
18
- }
18
+ },
19
+ "./dist/*.css": "./dist/*.css",
20
+ "./assets/*.css": "./assets/*.css"
19
21
  },
20
- "files": [
21
- "dist"
22
- ],
22
+ "files": ["*.md", "dist", "lib", "es"],
23
23
  "publishConfig": {
24
24
  "access": "public"
25
25
  },