@rpg-engine/long-bow 0.7.90 → 0.7.92

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 (35) hide show
  1. package/dist/components/CraftBook/CraftingTooltip.d.ts +13 -0
  2. package/dist/components/CraftBook/components/CraftBookHeader.d.ts +9 -0
  3. package/dist/components/CraftBook/components/CraftBookPagination.d.ts +0 -0
  4. package/dist/components/CraftBook/components/CraftBookSearch.d.ts +0 -0
  5. package/dist/components/CraftBook/hooks/useCraftBookFilters.d.ts +9 -0
  6. package/dist/components/CraftBook/hooks/useFilteredItems.d.ts +9 -0
  7. package/dist/components/CraftBook/hooks/usePagination.d.ts +13 -0
  8. package/dist/components/CraftBook/hooks/useResponsiveSize.d.ts +6 -0
  9. package/dist/components/CraftBook/utils/modifyString.d.ts +1 -0
  10. package/dist/components/shared/Pagination/Pagination.d.ts +9 -0
  11. package/dist/components/shared/SearchBar/SearchBar.d.ts +10 -0
  12. package/dist/hooks/useLocalStorage.d.ts +1 -0
  13. package/dist/long-bow.cjs.development.js +464 -289
  14. package/dist/long-bow.cjs.development.js.map +1 -1
  15. package/dist/long-bow.cjs.production.min.js +1 -1
  16. package/dist/long-bow.cjs.production.min.js.map +1 -1
  17. package/dist/long-bow.esm.js +464 -290
  18. package/dist/long-bow.esm.js.map +1 -1
  19. package/dist/stories/Features/craftbook/CraftBook.stories.d.ts +2 -0
  20. package/package.json +1 -1
  21. package/src/components/CraftBook/CraftBook.tsx +306 -121
  22. package/src/components/CraftBook/CraftingRecipe.tsx +97 -97
  23. package/src/components/CraftBook/CraftingTooltip.tsx +137 -0
  24. package/src/components/CraftBook/components/CraftBookHeader.tsx +81 -0
  25. package/src/components/CraftBook/components/CraftBookPagination.tsx +1 -0
  26. package/src/components/CraftBook/components/CraftBookSearch.tsx +1 -0
  27. package/src/components/CraftBook/hooks/useCraftBookFilters.ts +39 -0
  28. package/src/components/CraftBook/hooks/useFilteredItems.ts +39 -0
  29. package/src/components/CraftBook/hooks/usePagination.ts +39 -0
  30. package/src/components/CraftBook/hooks/useResponsiveSize.ts +50 -0
  31. package/src/components/CraftBook/utils/modifyString.ts +11 -0
  32. package/src/components/shared/Pagination/Pagination.tsx +69 -0
  33. package/src/components/shared/SearchBar/SearchBar.tsx +52 -0
  34. package/src/hooks/useLocalStorage.ts +44 -0
  35. package/src/stories/Features/craftbook/CraftBook.stories.tsx +41 -1
@@ -4,12 +4,13 @@ import {
4
4
  IItemContainer,
5
5
  ISkill,
6
6
  } from '@rpg-engine/shared';
7
- import React from 'react';
7
+ import React, { useEffect, useRef, useState } from 'react';
8
+ import { createPortal } from 'react-dom';
8
9
  import styled from 'styled-components';
9
- import { uiColors } from '../../constants/uiColors';
10
- import { countItemFromInventory } from '../../libs/itemCounter';
11
10
  import { ItemInfoWrapper } from '../Item/Cards/ItemInfoWrapper';
12
11
  import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
12
+ import { CraftingTooltip } from './CraftingTooltip';
13
+ import { modifyString } from './utils/modifyString';
13
14
 
14
15
  interface ICraftingRecipeProps {
15
16
  atlasJSON: any;
@@ -23,6 +24,44 @@ interface ICraftingRecipeProps {
23
24
  skills?: ISkill | null;
24
25
  }
25
26
 
27
+ const RadioOptionsWrapper = styled.div`
28
+ display: flex;
29
+ align-items: stretch;
30
+ margin-bottom: 0.5rem;
31
+ padding: 8px;
32
+ position: relative;
33
+ cursor: pointer;
34
+ `;
35
+
36
+ const SpriteAtlasWrapper = styled.div`
37
+ margin-right: 40px;
38
+ flex-shrink: 0;
39
+ width: 32px;
40
+ height: 32px;
41
+ display: flex;
42
+ align-items: center;
43
+ justify-content: center;
44
+ `;
45
+
46
+ const MainContent = styled.div`
47
+ flex: 1;
48
+ min-width: 0;
49
+ `;
50
+
51
+ const ItemHeader = styled.div`
52
+ display: flex;
53
+ align-items: center;
54
+ gap: 8px;
55
+ margin-bottom: 4px;
56
+
57
+ label {
58
+ font-size: 0.9rem;
59
+ font-weight: bold;
60
+ display: flex;
61
+ align-items: center;
62
+ }
63
+ `;
64
+
26
65
  export const CraftingRecipe: React.FC<ICraftingRecipeProps> = ({
27
66
  atlasIMG,
28
67
  atlasJSON,
@@ -34,32 +73,41 @@ export const CraftingRecipe: React.FC<ICraftingRecipeProps> = ({
34
73
  inventory,
35
74
  skills,
36
75
  }) => {
37
- const modifyString = (str: string) => {
38
- // Split the string by "/" and "."
39
- let parts = str.split('/');
40
- let fileName = parts[parts.length - 1];
41
- parts = fileName.split('.');
42
- let name = parts[0];
43
-
44
- // Replace all occurrences of "-" with " "
45
- name = name.replace(/-/g, ' ');
46
-
47
- // Uppercase the first word
48
- let words = name.split(' ');
49
- let firstWord = words[0].slice(0, 1).toUpperCase() + words[0].slice(1);
50
- let modifiedWords = [firstWord].concat(words.slice(1));
51
- name = modifiedWords.join(' ');
52
-
53
- return name;
76
+ const [showTooltip, setShowTooltip] = useState(false);
77
+ const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 });
78
+ const wrapperRef = useRef<HTMLDivElement>(null);
79
+
80
+ const isSelected = selectedCraftItemKey === recipe.key;
81
+
82
+ useEffect(() => {
83
+ const handleClickOutside = (event: MouseEvent) => {
84
+ if (
85
+ wrapperRef.current &&
86
+ !wrapperRef.current.contains(event.target as Node)
87
+ ) {
88
+ setShowTooltip(false);
89
+ }
90
+ };
91
+
92
+ document.addEventListener('mousedown', handleClickOutside);
93
+ return () => {
94
+ document.removeEventListener('mousedown', handleClickOutside);
95
+ };
96
+ }, []);
97
+
98
+ const handleClick = () => {
99
+ if (wrapperRef.current) {
100
+ const rect = wrapperRef.current.getBoundingClientRect();
101
+ setTooltipPosition({
102
+ x: rect.right + 10,
103
+ y: rect.top,
104
+ });
105
+ setShowTooltip(true);
106
+ }
54
107
  };
55
108
 
56
- const levelInSkill =
57
- (skills?.[
58
- (recipe?.minCraftingRequirements?.[0] ?? '') as keyof ISkill
59
- ] as any)?.level ?? 1;
60
-
61
109
  return (
62
- <RadioOptionsWrapper>
110
+ <RadioOptionsWrapper ref={wrapperRef} onClick={handleClick}>
63
111
  <SpriteAtlasWrapper>
64
112
  <ItemInfoWrapper
65
113
  item={recipe}
@@ -77,85 +125,37 @@ export const CraftingRecipe: React.FC<ICraftingRecipeProps> = ({
77
125
  />
78
126
  </ItemInfoWrapper>
79
127
  </SpriteAtlasWrapper>
80
- <div>
81
- <div onPointerUp={recipe.canCraft ? handleRecipeSelect : undefined}>
128
+
129
+ <MainContent>
130
+ <ItemHeader
131
+ onPointerUp={recipe.canCraft ? handleRecipeSelect : undefined}
132
+ >
82
133
  <input
83
134
  className="rpgui-radio"
84
135
  type="radio"
85
136
  value={recipe.name}
86
137
  name="test"
87
138
  disabled={!recipe.canCraft}
88
- checked={selectedCraftItemKey === recipe.key}
139
+ checked={isSelected}
89
140
  onChange={handleRecipeSelect}
90
141
  />
91
- <label style={{ display: 'flex', alignItems: 'center' }}>
92
- {modifyString(recipe.name)}
93
- </label>
94
- </div>
95
-
96
- <MinCraftingRequirementsText levelIsOk={recipe?.levelIsOk ?? false}>
97
- {modifyString(`${recipe?.minCraftingRequirements?.[0] ?? ''}`)} lvl{' '}
98
- {recipe?.minCraftingRequirements?.[1] ?? 0} ({levelInSkill})
99
- </MinCraftingRequirementsText>
100
-
101
- {recipe.ingredients.map((ingredient, index) => {
102
- const itemQtyInInventory = !inventory
103
- ? 0
104
- : countItemFromInventory(ingredient.key, inventory);
105
-
106
- return (
107
- <Recipe key={index}>
108
- <SpriteFromAtlas
109
- atlasIMG={atlasIMG}
110
- atlasJSON={atlasJSON}
111
- spriteKey={ingredient.texturePath}
112
- imgScale={1.2}
113
- />
114
- <Ingredient isQuantityOk={ingredient.qty <= itemQtyInInventory}>
115
- {modifyString(ingredient.key)} x{ingredient.qty} (
116
- {itemQtyInInventory})
117
- </Ingredient>
118
- </Recipe>
119
- );
120
- })}
121
- </div>
142
+ <label>{modifyString(recipe.name)}</label>
143
+ </ItemHeader>
144
+ </MainContent>
145
+
146
+ {showTooltip &&
147
+ createPortal(
148
+ <CraftingTooltip
149
+ x={tooltipPosition.x}
150
+ y={tooltipPosition.y}
151
+ recipe={recipe}
152
+ inventory={inventory}
153
+ skills={skills}
154
+ atlasIMG={atlasIMG}
155
+ atlasJSON={atlasJSON}
156
+ />,
157
+ document.body
158
+ )}
122
159
  </RadioOptionsWrapper>
123
160
  );
124
161
  };
125
-
126
- const Ingredient = styled.p<{ isQuantityOk: boolean }>`
127
- margin: 0;
128
- margin-left: 14px;
129
- color: ${({ isQuantityOk }) =>
130
- isQuantityOk ? uiColors.lightGreen : uiColors.lightGray} !important;
131
- `;
132
-
133
- const Recipe = styled.div`
134
- font-size: 0.6rem;
135
- margin-bottom: 3px;
136
- display: flex;
137
- align-items: center;
138
- margin-left: 4px;
139
-
140
- .sprite-from-atlas-img {
141
- top: 0px;
142
- left: 0px;
143
- }
144
- `;
145
-
146
- const SpriteAtlasWrapper = styled.div`
147
- margin-right: 40px;
148
- `;
149
-
150
- const RadioOptionsWrapper = styled.div`
151
- display: flex;
152
- align-items: stretch;
153
- margin-bottom: 40px;
154
- `;
155
-
156
- const MinCraftingRequirementsText = styled.p<{ levelIsOk: boolean }>`
157
- font-size: 0.6rem !important;
158
- margin: 0 5px 0 35px;
159
- color: ${({ levelIsOk }) =>
160
- levelIsOk ? uiColors.lightGreen : uiColors.lightGray} !important;
161
- `;
@@ -0,0 +1,137 @@
1
+ import { ICraftableItem, IItemContainer, ISkill } from '@rpg-engine/shared';
2
+ import React from 'react';
3
+ import styled from 'styled-components';
4
+ import { uiColors } from '../../constants/uiColors';
5
+ import { countItemFromInventory } from '../../libs/itemCounter';
6
+ import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
7
+ import { modifyString } from './utils/modifyString';
8
+
9
+ interface ICraftingTooltipProps {
10
+ x: number;
11
+ y: number;
12
+ recipe: ICraftableItem;
13
+ inventory?: IItemContainer | null;
14
+ skills?: ISkill | null;
15
+ atlasIMG: any;
16
+ atlasJSON: any;
17
+ }
18
+
19
+ const TooltipContainer = styled.div<{ x: number; y: number }>`
20
+ position: fixed;
21
+ left: ${props => props.x}px;
22
+ top: ${props => props.y}px;
23
+ background-color: rgba(0, 0, 0, 0.95);
24
+ color: white;
25
+ text-align: left;
26
+ border-radius: 4px;
27
+ padding: 10px;
28
+ z-index: 1000;
29
+ font-family: 'Press Start 2P', cursive;
30
+ font-size: 0.6rem;
31
+ width: max-content;
32
+ max-width: 250px;
33
+ white-space: normal;
34
+ border: 1px solid ${uiColors.darkGray};
35
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
36
+ line-height: 1.4;
37
+
38
+ /* Arrow */
39
+ &:before {
40
+ content: '';
41
+ position: absolute;
42
+ top: 12px;
43
+ left: -6px;
44
+ border-width: 6px 6px 6px 0;
45
+ border-style: solid;
46
+ border-color: transparent rgba(0, 0, 0, 0.95) transparent transparent;
47
+ }
48
+ `;
49
+
50
+ const MinCraftingRequirementsText = styled.div<{ levelIsOk: boolean }>`
51
+ font-size: 0.55rem;
52
+ margin: 0;
53
+ margin-bottom: 12px;
54
+ color: ${({ levelIsOk }) =>
55
+ levelIsOk ? uiColors.lightGreen : uiColors.lightGray} !important;
56
+ `;
57
+
58
+ const IngredientsTitle = styled.div`
59
+ color: ${uiColors.yellow};
60
+ font-size: 0.6rem;
61
+ margin-bottom: 12px;
62
+ text-transform: uppercase;
63
+ letter-spacing: 0.5px;
64
+ `;
65
+
66
+ const Recipe = styled.div`
67
+ display: flex;
68
+ align-items: center;
69
+ font-size: 0.55rem;
70
+ margin-bottom: 8px;
71
+ margin-left: 4px;
72
+
73
+ &:last-child {
74
+ margin-bottom: 0;
75
+ }
76
+
77
+ .sprite-from-atlas-img {
78
+ top: 0px;
79
+ left: 0px;
80
+ }
81
+ `;
82
+
83
+ const Ingredient = styled.div<{ isQuantityOk: boolean }>`
84
+ margin: 0;
85
+ margin-left: 14px;
86
+ color: ${({ isQuantityOk }) =>
87
+ isQuantityOk ? uiColors.lightGreen : uiColors.lightGray} !important;
88
+ `;
89
+
90
+ export const CraftingTooltip: React.FC<ICraftingTooltipProps> = ({
91
+ x,
92
+ y,
93
+ recipe,
94
+ inventory,
95
+ skills,
96
+ atlasIMG,
97
+ atlasJSON,
98
+ }) => {
99
+ const levelInSkill =
100
+ (skills?.[
101
+ (recipe?.minCraftingRequirements?.[0] ?? '') as keyof ISkill
102
+ ] as any)?.level ?? 1;
103
+
104
+ const levelIsOk = recipe?.levelIsOk ?? false;
105
+
106
+ return (
107
+ <TooltipContainer x={x} y={y}>
108
+ <MinCraftingRequirementsText levelIsOk={levelIsOk}>
109
+ {modifyString(`${recipe?.minCraftingRequirements?.[0] ?? ''}`)} lvl{' '}
110
+ {recipe?.minCraftingRequirements?.[1] ?? 0} ({levelInSkill})
111
+ </MinCraftingRequirementsText>
112
+
113
+ <IngredientsTitle>Ingredients</IngredientsTitle>
114
+ {recipe.ingredients.map((ingredient, index) => {
115
+ const itemQtyInInventory = !inventory
116
+ ? 0
117
+ : countItemFromInventory(ingredient.key, inventory);
118
+ const isQuantityOk = ingredient.qty <= itemQtyInInventory;
119
+
120
+ return (
121
+ <Recipe key={index}>
122
+ <SpriteFromAtlas
123
+ atlasIMG={atlasIMG}
124
+ atlasJSON={atlasJSON}
125
+ spriteKey={ingredient.texturePath}
126
+ imgScale={1.2}
127
+ />
128
+ <Ingredient isQuantityOk={isQuantityOk}>
129
+ {modifyString(ingredient.key)} x{ingredient.qty} (
130
+ {itemQtyInInventory})
131
+ </Ingredient>
132
+ </Recipe>
133
+ );
134
+ })}
135
+ </TooltipContainer>
136
+ );
137
+ };
@@ -0,0 +1,81 @@
1
+ import React from 'react';
2
+ import { FaSearch } from 'react-icons/fa';
3
+ import styled from 'styled-components';
4
+ import { uiColors } from '../../../constants/uiColors';
5
+ import { Dropdown, IOptionsProps } from '../../Dropdown';
6
+
7
+ interface CraftBookHeaderProps {
8
+ categoryOptions: IOptionsProps[];
9
+ onCategoryChange: (value: string) => void;
10
+ onSearchToggle: () => void;
11
+ }
12
+
13
+ export const CraftBookHeader: React.FC<CraftBookHeaderProps> = ({
14
+ categoryOptions,
15
+ onCategoryChange,
16
+ onSearchToggle,
17
+ }) => {
18
+ return (
19
+ <Container>
20
+ <Title>Craftbook</Title>
21
+ <Controls>
22
+ <DropdownWrapper>
23
+ <Dropdown
24
+ options={categoryOptions}
25
+ onChange={onCategoryChange}
26
+ width="200px"
27
+ />
28
+ </DropdownWrapper>
29
+ <SearchButton onClick={onSearchToggle}>
30
+ <FaSearch size={16} />
31
+ </SearchButton>
32
+ </Controls>
33
+ </Container>
34
+ );
35
+ };
36
+
37
+ const Container = styled.div`
38
+ display: flex;
39
+ justify-content: space-between;
40
+ align-items: center;
41
+ width: 100%;
42
+ padding: 16px 16px 0;
43
+ `;
44
+
45
+ const Title = styled.h1`
46
+ font-size: 1.2rem;
47
+ color: ${uiColors.yellow} !important;
48
+ margin: 0;
49
+ text-shadow: 2px 2px 2px rgba(0, 0, 0, 0.5);
50
+ `;
51
+
52
+ const Controls = styled.div`
53
+ display: flex;
54
+ align-items: center;
55
+ gap: 16px;
56
+ position: relative;
57
+ left: -2rem;
58
+ `;
59
+
60
+ const DropdownWrapper = styled.div`
61
+ width: 200px;
62
+ `;
63
+
64
+ const SearchButton = styled.button`
65
+ background: none;
66
+ border: none;
67
+ cursor: pointer;
68
+ padding: 8px;
69
+ width: 32px;
70
+ height: 32px;
71
+ color: ${uiColors.yellow};
72
+ opacity: 0.8;
73
+ transition: opacity 0.2s;
74
+ display: flex;
75
+ align-items: center;
76
+ justify-content: center;
77
+
78
+ &:hover {
79
+ opacity: 1;
80
+ }
81
+ `;
@@ -0,0 +1,39 @@
1
+ import { ICraftableItem } from '@rpg-engine/shared';
2
+ import { useMemo } from 'react';
3
+
4
+ interface UseCraftBookFiltersProps {
5
+ items: ICraftableItem[];
6
+ searchTerm: string;
7
+ selectedType: string;
8
+ pinnedItems: string[];
9
+ }
10
+
11
+ export const useCraftBookFilters = ({
12
+ items,
13
+ searchTerm,
14
+ selectedType,
15
+ pinnedItems,
16
+ }: UseCraftBookFiltersProps): ICraftableItem[] => {
17
+ return useMemo(() => {
18
+ // First filter items
19
+ const filteredItems = items?.filter(item => {
20
+ const matchesSearch = item.name
21
+ .toLowerCase()
22
+ .includes(searchTerm.toLowerCase());
23
+ const matchesCategory =
24
+ selectedType === 'Suggested' ||
25
+ (selectedType === 'Pinned' && pinnedItems.includes(item.key)) ||
26
+ item.type === selectedType;
27
+ return matchesSearch && matchesCategory;
28
+ });
29
+
30
+ // Then sort them (pinned items first)
31
+ return [...(filteredItems || [])].sort((a, b) => {
32
+ const aIsPinned = pinnedItems.includes(a.key);
33
+ const bIsPinned = pinnedItems.includes(b.key);
34
+ if (aIsPinned && !bIsPinned) return -1;
35
+ if (!aIsPinned && bIsPinned) return 1;
36
+ return 0;
37
+ });
38
+ }, [items, searchTerm, selectedType, pinnedItems]);
39
+ };
@@ -0,0 +1,39 @@
1
+ import { ICraftableItem } from '@rpg-engine/shared';
2
+ import { useMemo } from 'react';
3
+
4
+ interface UseFilteredItemsProps {
5
+ items: ICraftableItem[];
6
+ searchTerm: string;
7
+ selectedType: string;
8
+ pinnedItems: string[];
9
+ }
10
+
11
+ export const useFilteredItems = ({
12
+ items,
13
+ searchTerm,
14
+ selectedType,
15
+ pinnedItems,
16
+ }: UseFilteredItemsProps): ICraftableItem[] => {
17
+ return useMemo(() => {
18
+ // First filter items
19
+ const filteredItems = items?.filter(item => {
20
+ const matchesSearch = item.name
21
+ .toLowerCase()
22
+ .includes(searchTerm.toLowerCase());
23
+ const matchesCategory =
24
+ selectedType === 'Suggested' ||
25
+ (selectedType === 'Pinned' && pinnedItems.includes(item.key)) ||
26
+ item.type === selectedType;
27
+ return matchesSearch && matchesCategory;
28
+ });
29
+
30
+ // Then sort them (pinned items first)
31
+ return [...(filteredItems || [])].sort((a, b) => {
32
+ const aIsPinned = pinnedItems.includes(a.key);
33
+ const bIsPinned = pinnedItems.includes(b.key);
34
+ if (aIsPinned && !bIsPinned) return -1;
35
+ if (!aIsPinned && bIsPinned) return 1;
36
+ return 0;
37
+ });
38
+ }, [items, searchTerm, selectedType, pinnedItems]);
39
+ };
@@ -0,0 +1,39 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ interface UsePaginationProps<T> {
4
+ items: T[];
5
+ itemsPerPage: number;
6
+ dependencies?: any[];
7
+ }
8
+
9
+ interface UsePaginationReturn<T> {
10
+ currentPage: number;
11
+ setCurrentPage: (page: number) => void;
12
+ paginatedItems: T[];
13
+ totalPages: number;
14
+ }
15
+
16
+ export const usePagination = <T>({
17
+ items,
18
+ itemsPerPage,
19
+ dependencies = [],
20
+ }: UsePaginationProps<T>): UsePaginationReturn<T> => {
21
+ const [currentPage, setCurrentPage] = useState(1);
22
+ const totalPages = Math.ceil(items.length / itemsPerPage);
23
+
24
+ const paginatedItems = items.slice(
25
+ (currentPage - 1) * itemsPerPage,
26
+ currentPage * itemsPerPage
27
+ );
28
+
29
+ useEffect(() => {
30
+ setCurrentPage(1);
31
+ }, [...dependencies]);
32
+
33
+ return {
34
+ currentPage,
35
+ setCurrentPage,
36
+ paginatedItems,
37
+ totalPages,
38
+ };
39
+ };
@@ -0,0 +1,50 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ const desktop = {
4
+ width: 'min(900px, 80%)',
5
+ height: 'min(700px, 80%)',
6
+ };
7
+
8
+ const mobileLanscape = {
9
+ width: '800px',
10
+ height: '500px',
11
+ };
12
+
13
+ const mobilePortrait = {
14
+ width: '500px',
15
+ height: '700px',
16
+ };
17
+
18
+ interface Size {
19
+ width: string;
20
+ height: string;
21
+ }
22
+
23
+ export const useResponsiveSize = (scale?: number): Size | undefined => {
24
+ const [size, setSize] = useState<Size>();
25
+
26
+ useEffect(() => {
27
+ const handleResize = (): void => {
28
+ if (
29
+ window.innerWidth < 500 &&
30
+ size?.width !== mobilePortrait.width &&
31
+ (!scale || scale < 1)
32
+ ) {
33
+ setSize(mobilePortrait);
34
+ } else if (
35
+ (!scale || scale < 1) &&
36
+ size?.width !== mobileLanscape.width
37
+ ) {
38
+ setSize(mobileLanscape);
39
+ } else if (size?.width !== desktop.width) {
40
+ setSize(desktop);
41
+ }
42
+ };
43
+ handleResize();
44
+
45
+ window.addEventListener('resize', handleResize);
46
+ return () => window.removeEventListener('resize', handleResize);
47
+ }, [scale]);
48
+
49
+ return size;
50
+ };
@@ -0,0 +1,11 @@
1
+ export const modifyString = (str: string): string => {
2
+ let parts = str.split('/');
3
+ let fileName = parts[parts.length - 1];
4
+ parts = fileName.split('.');
5
+ let name = parts[0];
6
+ name = name.replace(/-/g, ' ');
7
+ let words = name.split(' ');
8
+ let firstWord = words[0].slice(0, 1).toUpperCase() + words[0].slice(1);
9
+ let modifiedWords = [firstWord].concat(words.slice(1));
10
+ return modifiedWords.join(' ');
11
+ };