Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
F
frontend
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
vicotor
frontend
Commits
602755e4
Commit
602755e4
authored
Mar 14, 2025
by
tom
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add rating component
parent
8a0c8788
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
185 additions
and
75 deletions
+185
-75
eslint.config.mjs
eslint.config.mjs
+1
-0
rating.tsx
toolkit/chakra/rating.tsx
+40
-0
semanticTokens.ts
toolkit/theme/foundations/semanticTokens.ts
+4
-0
index.ts
toolkit/theme/recipes/index.ts
+2
-0
rating-group.recipe.ts
toolkit/theme/recipes/rating-group.recipe.ts
+93
-0
PopoverContent.tsx
ui/marketplace/Rating/PopoverContent.tsx
+9
-33
Rating.tsx
ui/marketplace/Rating/Rating.tsx
+4
-4
Stars.tsx
ui/marketplace/Rating/Stars.tsx
+0
-37
Chakra.tsx
ui/pages/Chakra.tsx
+2
-0
AccountActionsMenu.tsx
ui/shared/AccountActionsMenu/AccountActionsMenu.tsx
+0
-1
Rating.tsx
ui/showcases/Rating.tsx
+30
-0
No files found.
eslint.config.mjs
View file @
602755e4
...
...
@@ -41,6 +41,7 @@ const RESTRICTED_MODULES = {
'
Heading
'
,
'
Badge
'
,
'
Tabs
'
,
'
Show
'
,
'
Hide
'
,
'
Checkbox
'
,
'
CheckboxGroup
'
,
'
Table
'
,
'
TableRoot
'
,
'
TableBody
'
,
'
TableHeader
'
,
'
TableRow
'
,
'
TableCell
'
,
'
Menu
'
,
'
MenuRoot
'
,
'
MenuTrigger
'
,
'
MenuContent
'
,
'
MenuItem
'
,
'
MenuTriggerItem
'
,
'
MenuRadioItemGroup
'
,
'
MenuContextTrigger
'
,
'
Rating
'
,
'
RatingGroup
'
,
],
message
:
'
Please use corresponding component or hook from ui/shared/chakra component instead
'
,
},
...
...
toolkit/chakra/rating.tsx
0 → 100644
View file @
602755e4
import
{
RatingGroup
,
useRatingGroup
}
from
'
@chakra-ui/react
'
;
import
*
as
React
from
'
react
'
;
import
IconSvg
from
'
ui/shared/IconSvg
'
;
export
interface
RatingProps
extends
Omit
<
RatingGroup
.
RootProviderProps
,
'
value
'
>
{
count
?:
number
;
label
?:
string
|
Array
<
string
>
;
defaultValue
?:
number
;
onValueChange
?:
({
value
}:
{
value
:
number
})
=>
void
;
readOnly
?:
boolean
;
}
export
const
Rating
=
React
.
forwardRef
<
HTMLDivElement
,
RatingProps
>
(
function
Rating
(
props
,
ref
)
{
const
{
count
=
5
,
label
:
labelProp
,
defaultValue
,
onValueChange
,
readOnly
,
...
rest
}
=
props
;
const
store
=
useRatingGroup
({
count
,
defaultValue
,
onValueChange
,
readOnly
});
const
highlightedIndex
=
store
.
hovering
&&
!
readOnly
?
store
.
hoveredValue
:
store
.
value
;
const
label
=
Array
.
isArray
(
labelProp
)
?
labelProp
[
highlightedIndex
-
1
]
:
labelProp
;
return
(
<
RatingGroup
.
RootProvider
ref=
{
ref
}
value=
{
store
}
{
...
rest
}
>
<
RatingGroup
.
HiddenInput
/>
<
RatingGroup
.
Control
>
{
Array
.
from
({
length
:
count
}).
map
((
_
,
index
)
=>
{
const
icon
=
index
<
highlightedIndex
?
<
IconSvg
name=
"star_filled"
/>
:
<
IconSvg
name=
"star_outline"
/>;
return
(
<
RatingGroup
.
Item
key=
{
index
}
index=
{
index
+
1
}
>
<
RatingGroup
.
ItemIndicator
icon=
{
icon
}
/>
</
RatingGroup
.
Item
>
);
})
}
</
RatingGroup
.
Control
>
{
label
&&
<
RatingGroup
.
Label
>
{
label
}
</
RatingGroup
.
Label
>
}
</
RatingGroup
.
RootProvider
>
);
},
);
toolkit/theme/foundations/semanticTokens.ts
View file @
602755e4
...
...
@@ -404,6 +404,10 @@ const semanticTokens: ThemingConfig['semanticTokens'] = {
down
:
{
value
:
{
_light
:
'
{colors.red.600}
'
,
_dark
:
'
{colors.red.400}
'
}
},
},
},
rating
:
{
DEFAULT
:
{
value
:
{
_light
:
'
{colors.gray.200}
'
,
_dark
:
'
{colors.gray.700}
'
}
},
highlighted
:
{
value
:
'
{colors.yellow.400}
'
},
},
heading
:
{
DEFAULT
:
{
value
:
{
_light
:
'
{colors.blackAlpha.800}
'
,
_dark
:
'
{colors.whiteAlpha.800}
'
}
},
},
...
...
toolkit/theme/recipes/index.ts
View file @
602755e4
...
...
@@ -18,6 +18,7 @@ import { recipe as popover } from './popover.recipe';
import
{
recipe
as
progressCircle
}
from
'
./progress-circle.recipe
'
;
import
{
recipe
as
radioGroup
}
from
'
./radio-group.recipe
'
;
import
{
recipe
as
radiomark
}
from
'
./radiomark.recipe
'
;
import
{
recipe
as
ratingGroup
}
from
'
./rating-group.recipe
'
;
import
{
recipe
as
select
}
from
'
./select.recipe
'
;
import
{
recipe
as
skeleton
}
from
'
./skeleton.recipe
'
;
import
{
recipe
as
spinner
}
from
'
./spinner.recipe
'
;
...
...
@@ -57,6 +58,7 @@ export const slotRecipes = {
popover
,
progressCircle
,
radioGroup
,
ratingGroup
,
select
,
stat
,
'
switch
'
:
switchRecipe
,
...
...
toolkit/theme/recipes/rating-group.recipe.ts
0 → 100644
View file @
602755e4
import
{
defineSlotRecipe
}
from
'
@chakra-ui/react
'
;
export
const
recipe
=
defineSlotRecipe
({
className
:
'
chakra-rating-group
'
,
slots
:
[
'
root
'
,
'
control
'
,
'
item
'
,
'
itemIndicator
'
],
base
:
{
root
:
{
display
:
'
inline-flex
'
,
alignItems
:
'
center
'
,
columnGap
:
3
,
},
control
:
{
display
:
'
inline-flex
'
,
alignItems
:
'
center
'
,
gap
:
1
,
},
item
:
{
display
:
'
inline-flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
center
'
,
userSelect
:
'
none
'
,
cursor
:
'
pointer
'
,
_icon
:
{
width
:
'
100%
'
,
height
:
'
100%
'
,
display
:
'
inline-block
'
,
flexShrink
:
0
,
position
:
'
absolute
'
,
left
:
0
,
top
:
0
,
},
},
itemIndicator
:
{
display
:
'
inline-flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
center
'
,
position
:
'
relative
'
,
_icon
:
{
stroke
:
'
none
'
,
width
:
'
100%
'
,
height
:
'
100%
'
,
display
:
'
inline-block
'
,
flexShrink
:
0
,
position
:
'
absolute
'
,
left
:
0
,
top
:
0
,
},
'
& [data-bg]
'
:
{
color
:
'
rating
'
,
},
'
& [data-fg]
'
:
{
color
:
'
transparent
'
,
},
'
&[data-highlighted]:not([data-half])
'
:
{
'
& [data-fg]
'
:
{
color
:
'
rating.highlighted
'
,
},
},
'
&[data-half]
'
:
{
'
& [data-fg]
'
:
{
color
:
'
rating.highlighted
'
,
clipPath
:
'
inset(0 50% 0 0)
'
,
},
},
},
},
variants
:
{
size
:
{
md
:
{
item
:
{
boxSize
:
5
,
},
root
:
{
textStyle
:
'
md
'
,
},
},
},
},
defaultVariants
:
{
size
:
'
md
'
,
},
});
ui/marketplace/Rating/PopoverContent.tsx
View file @
602755e4
...
...
@@ -4,9 +4,9 @@ import React from 'react';
import
type
{
AppRating
}
from
'
types/client/marketplace
'
;
import
type
{
EventTypes
,
EventPayload
}
from
'
lib/mixpanel/index
'
;
import
{
Rating
}
from
'
toolkit/chakra/rating
'
;
import
IconSvg
from
'
ui/shared/IconSvg
'
;
import
Stars
from
'
./Stars
'
;
import
type
{
RateFunction
}
from
'
./useRatings
'
;
const
ratingDescriptions
=
[
'
Very bad
'
,
'
Bad
'
,
'
Average
'
,
'
Good
'
,
'
Excellent
'
];
...
...
@@ -21,25 +21,8 @@ type Props = {
};
const
PopoverContent
=
({
appId
,
rating
,
userRating
,
rate
,
isSending
,
source
}:
Props
)
=>
{
const
[
hovered
,
setHovered
]
=
React
.
useState
(
-
1
);
const
filledIndex
=
React
.
useMemo
(()
=>
{
if
(
hovered
>=
0
)
{
return
hovered
;
}
return
userRating
?.
value
?
userRating
?.
value
-
1
:
-
1
;
},
[
userRating
,
hovered
]);
const
handleMouseOverFactory
=
React
.
useCallback
((
index
:
number
)
=>
()
=>
{
setHovered
(
index
);
},
[]);
const
handleMouseOut
=
React
.
useCallback
(()
=>
{
setHovered
(
-
1
);
},
[]);
const
handleRateFactory
=
React
.
useCallback
((
index
:
number
)
=>
()
=>
{
rate
(
appId
,
rating
?.
recordId
,
userRating
?.
recordId
,
index
+
1
,
source
);
const
handleValueChange
=
React
.
useCallback
(({
value
}:
{
value
:
number
})
=>
{
rate
(
appId
,
rating
?.
recordId
,
userRating
?.
recordId
,
value
,
source
);
},
[
appId
,
rating
,
rate
,
userRating
,
source
]);
if
(
isSending
)
{
...
...
@@ -61,19 +44,12 @@ const PopoverContent = ({ appId, rating, userRating, rate, isSending, source }:
{
userRating
?
'
App is already rated by you
'
:
'
How was your experience?
'
}
</
Text
>
</
Flex
>
<
Flex
alignItems=
"center"
h=
"32px"
>
<
Stars
filledIndex=
{
filledIndex
}
onMouseOverFactory=
{
handleMouseOverFactory
}
onMouseOut=
{
handleMouseOut
}
onClickFactory=
{
handleRateFactory
}
/>
{
(
filledIndex
>=
0
)
&&
(
<
Text
fontSize=
"md"
ml=
{
3
}
>
{
ratingDescriptions
[
filledIndex
]
}
</
Text
>
)
}
</
Flex
>
<
Rating
defaultValue=
{
userRating
?.
value
}
onValueChange=
{
handleValueChange
}
label=
{
ratingDescriptions
}
mt=
{
1
}
/>
</>
);
};
...
...
ui/marketplace/Rating/Rating.tsx
View file @
602755e4
...
...
@@ -6,10 +6,10 @@ import type { AppRating } from 'types/client/marketplace';
import
config
from
'
configs/app
'
;
import
type
{
EventTypes
,
EventPayload
}
from
'
lib/mixpanel/index
'
;
import
{
PopoverBody
,
PopoverContent
,
PopoverRoot
}
from
'
toolkit/chakra/popover
'
;
import
{
Rating
}
from
'
toolkit/chakra/rating
'
;
import
{
Skeleton
}
from
'
toolkit/chakra/skeleton
'
;
import
Content
from
'
./PopoverContent
'
;
import
Stars
from
'
./Stars
'
;
import
TriggerButton
from
'
./TriggerButton
'
;
import
type
{
RateFunction
}
from
'
./useRatings
'
;
...
...
@@ -28,7 +28,7 @@ type Props = {
source
:
EventPayload
<
EventTypes
.
APP_FEEDBACK
>
[
'
Source
'
];
};
const
Rating
=
({
const
Marketplace
Rating
=
({
appId
,
rating
,
userRating
,
rate
,
isSending
,
isLoading
,
fullView
,
canRate
,
source
,
}:
Props
)
=>
{
...
...
@@ -46,7 +46,7 @@ const Rating = ({
>
{
fullView
&&
(
<>
<
Stars
filledIndex=
{
(
rating
?.
value
||
0
)
-
1
}
/>
<
Rating
defaultValue=
{
Math
.
floor
(
rating
?.
value
||
0
)
}
readOnly
key=
{
rating
?.
value
}
/>
<
Text
fontSize=
"md"
ml=
{
2
}
>
{
rating
?.
value
}
</
Text
>
{
rating
?.
count
&&
<
Text
color=
"text.secondary"
textStyle=
"md"
ml=
{
1
}
>
(
{
rating
?.
count
}
)
</
Text
>
}
</>
...
...
@@ -78,4 +78,4 @@ const Rating = ({
);
};
export
default
Rating
;
export
default
Marketplace
Rating
;
ui/marketplace/Rating/Stars.tsx
deleted
100644 → 0
View file @
8a0c8788
import
{
Flex
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
type
{
MouseEventHandler
}
from
'
react
'
;
import
IconSvg
from
'
ui/shared/IconSvg
'
;
type
Props
=
{
filledIndex
:
number
;
onMouseOverFactory
?:
(
index
:
number
)
=>
MouseEventHandler
<
HTMLDivElement
>
;
onMouseOut
?:
()
=>
void
;
onClickFactory
?:
(
index
:
number
)
=>
MouseEventHandler
<
HTMLDivElement
>
;
};
const
Stars
=
({
filledIndex
,
onMouseOverFactory
,
onMouseOut
,
onClickFactory
}:
Props
)
=>
{
const
outlineStartColor
=
onMouseOverFactory
?
'
gray.400
'
:
{
_light
:
'
gray.200
'
,
_dark
:
'
gray.700
'
};
return
(
<
Flex
>
{
Array
(
5
).
fill
(
null
).
map
((
_
,
index
)
=>
(
<
IconSvg
key=
{
index
}
name=
{
filledIndex
>=
index
?
'
star_filled
'
:
'
star_outline
'
}
color=
{
filledIndex
>=
index
?
'
yellow.400
'
:
outlineStartColor
}
w=
{
6
}
// 5 + 1 padding
h=
{
5
}
pr=
{
1
}
// use padding intead of margin so that there are no empty spaces between stars without hover effect
_last=
{
{
w
:
5
,
pr
:
0
}
}
cursor=
{
onMouseOverFactory
?
'
pointer
'
:
'
default
'
}
onMouseOver=
{
onMouseOverFactory
?.(
index
)
}
onMouseOut=
{
onMouseOut
}
onClick=
{
onClickFactory
?.(
index
)
}
/>
))
}
</
Flex
>
);
};
export
default
Stars
;
ui/pages/Chakra.tsx
View file @
602755e4
...
...
@@ -24,6 +24,7 @@ import PinInputShowcase from 'ui/showcases/PinInput';
import
PopoverShowcase
from
'
ui/showcases/Popover
'
;
import
ProgressCircleShowcase
from
'
ui/showcases/ProgressCircle
'
;
import
RadioShowcase
from
'
ui/showcases/Radio
'
;
import
RatingShowcase
from
'
ui/showcases/Rating
'
;
import
SelectShowcase
from
'
ui/showcases/Select
'
;
import
SkeletonShowcase
from
'
ui/showcases/Skeleton
'
;
import
SpinnerShowcase
from
'
ui/showcases/Spinner
'
;
...
...
@@ -61,6 +62,7 @@ const tabs = [
{
label
:
'
Pagination
'
,
value
:
'
pagination
'
,
component
:
<
PaginationShowcase
/>
},
{
label
:
'
Progress Circle
'
,
value
:
'
progress-circle
'
,
component
:
<
ProgressCircleShowcase
/>
},
{
label
:
'
Radio
'
,
value
:
'
radio
'
,
component
:
<
RadioShowcase
/>
},
{
label
:
'
Rating
'
,
value
:
'
rating
'
,
component
:
<
RatingShowcase
/>
},
{
label
:
'
Pin input
'
,
value
:
'
pin-input
'
,
component
:
<
PinInputShowcase
/>
},
{
label
:
'
Popover
'
,
value
:
'
popover
'
,
component
:
<
PopoverShowcase
/>
},
{
label
:
'
Select
'
,
value
:
'
select
'
,
component
:
<
SelectShowcase
/>
},
...
...
ui/shared/AccountActionsMenu/AccountActionsMenu.tsx
View file @
602755e4
...
...
@@ -24,7 +24,6 @@ interface Props {
showUpdateMetadataItem
?:
boolean
;
}
// TODO @tom2drum fix modal open on menu item click
const
AccountActionsMenu
=
({
isLoading
,
className
,
showUpdateMetadataItem
}:
Props
)
=>
{
const
router
=
useRouter
();
...
...
ui/showcases/Rating.tsx
0 → 100644
View file @
602755e4
import
React
from
'
react
'
;
import
{
Rating
}
from
'
toolkit/chakra/rating
'
;
import
{
Section
,
Container
,
SectionHeader
,
SamplesStack
,
Sample
}
from
'
./parts
'
;
const
RatingShowcase
=
()
=>
{
return
(
<
Container
value=
"rating"
>
<
Section
>
<
SectionHeader
>
Size
</
SectionHeader
>
<
SamplesStack
>
<
Sample
label=
"size: md"
>
<
Rating
defaultValue=
{
3
}
label=
{
[
'
Very bad
'
,
'
Bad
'
,
'
Average
'
,
'
Good
'
,
'
Excellent
'
]
}
/>
</
Sample
>
</
SamplesStack
>
</
Section
>
<
Section
>
<
SectionHeader
>
Read-only
</
SectionHeader
>
<
SamplesStack
>
<
Sample
label=
"readOnly: true"
>
<
Rating
defaultValue=
{
3
}
label=
{
[
'
Very bad
'
,
'
Bad
'
,
'
Average
'
,
'
Good
'
,
'
Excellent
'
]
}
readOnly
/>
</
Sample
>
</
SamplesStack
>
</
Section
>
</
Container
>
);
};
export
default
React
.
memo
(
RatingShowcase
);
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment