feat: prepare project for cozy-ui migration (#157)

* feat: prepare project for cozy-ui migration

Build Configuration
- Add Stylus support via @rsbuild/plugin-stylus
- Add SVGR support via @rsbuild/plugin-svgr
- Update rsbuild.config.ts with custom configuration
- Add historyApiFallback for SPA routing

ESLint Configuration
- Create .eslintrc.json with react-app configuration
- Remove eslintConfig from package.json

CSS to Stylus Migration
Convert all 5 CSS files to Stylus (.styl) format:
  - src/App.css to src/App.styl
  - src/index.css to src/index.styl
  - src/components/Calendar/Calendar.css to src/components/Calendar/Calendar.styl
  - src/components/Calendar/CustomCalendar.css to src/components/Calendar/CustomCalendar.styl
  - src/components/Menubar/Menubar.css to src/components/Menubar/Menubar.styl
- Update all imports from .css to .styl
- Preserve all styling rules and values

MUI Styling Cleanup
- Replace all MUI sx props with standard style props across 15+ components
- Convert MUI spacing units (mr: 2 to marginRight: 16px)
- Convert MUI shorthand props (p: 2 to padding: 16px, mb: 1 to marginBottom: 8px)
- Remove MUI-specific syntax (&.Mui-checked selectors)
- Maintain exact visual appearance

FullCalendar Fixes
- Comment out problematic CSS rule in CustomCalendar.styl
- Fix display issues with timegrid slot labels

Files Modified
- Config: rsbuild.config.ts, .eslintrc.json, package.json
- Styling: 5 CSS to Stylus conversions, 15+ component sx to style updates
- Components: All components using MUI sx props updated

Development Workflow
- Continue using existing development commands
- Stylus files are now the source of truth for styling
- MUI components work with standard style props
- ESLint uses familiar react-app rules

Dependencies Added
- @rsbuild/plugin-stylus
- @rsbuild/plugin-svgr
This commit is contained in:
lenhanphung
2025-10-01 22:31:18 +07:00
committed by GitHub
parent 17c6bf7bce
commit 3870bceb6d
25 changed files with 1682 additions and 661 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"extends": ["react-app", "react-app/jest"]
}
+1080 -51
View File
File diff suppressed because it is too large Load Diff
+13 -7
View File
@@ -36,15 +36,9 @@
"build": "rsbuild build",
"preview": "rsbuild preview",
"test": "jest",
"lint": "eslint src",
"lint": "eslint src --ext .js,.jsx,.ts,.tsx",
"jest-preview": "jest-preview"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
@@ -61,23 +55,35 @@
"node": "24.x"
},
"devDependencies": {
"@babel/eslint-parser": "^7.28.4",
"@babel/preset-env": "^7.28.0",
"@babel/preset-react": "^7.27.1",
"@rsbuild/core": "^1.5.2",
"@rsbuild/plugin-react": "^1.3.3",
"@rsbuild/plugin-stylus": "^1.2.0",
"@rsbuild/plugin-svgr": "^1.2.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^30.0.0",
"@typescript-eslint/eslint-plugin": "^8.44.1",
"@typescript-eslint/parser": "^8.44.1",
"babel-jest": "^30.0.4",
"eslint": "^8.57.1",
"eslint-config-prettier": "^10.1.8",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest": "^29.0.1",
"eslint-plugin-promise": "^7.2.1",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"history": "^5.3.0",
"i18next-resources-for-ts": "1.4.0",
"jest": "^30.0.4",
"jest-environment-jsdom": "^30.0.4",
"jest-preview": "^0.3.1",
"prettier": "^3.6.2",
"ts-jest": "^29.4.0",
"typescript": "^4.9.5"
}
+17 -2
View File
@@ -1,10 +1,25 @@
import { defineConfig } from "@rsbuild/core";
import { pluginReact } from "@rsbuild/plugin-react";
import { pluginStylus } from "@rsbuild/plugin-stylus";
import { pluginSvgr } from "@rsbuild/plugin-svgr";
export default defineConfig({
plugins: [pluginReact()],
plugins: [pluginReact(), pluginStylus(), pluginSvgr()],
html: {
template: "./public/index.html",
},
server: { port: 5000 },
server: {
port: 5000,
historyApiFallback: true,
},
source: {
entry: {
index: "./src/index.tsx",
},
},
output: {
distPath: {
root: "dist",
},
},
});
-20
View File
@@ -1,20 +0,0 @@
.App {
text-align: center;
font-family: "Roboto";
height: 100vh;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
+17
View File
@@ -0,0 +1,17 @@
.App
text-align center
font-family "Roboto"
height 100vh
.App-header
background-color #282c34
min-height 100vh
display flex
flex-direction column
align-items center
justify-content center
font-size calc(10px + 2vmin)
color white
.App-link
color #61dafb
+1 -1
View File
@@ -3,7 +3,7 @@ import { Route, Routes } from "react-router-dom";
import { HistoryRouter as Router } from "redux-first-history/rr6";
import { CallbackResume } from "./features/User/LoginCallback";
import { history } from "./app/store";
import "./App.css";
import "./App.styl";
import { Loading } from "./components/Loading/Loading";
import HandleLogin from "./features/User/HandleLogin";
import CalendarLayout from "./components/Calendar/CalendarLayout";
+2 -2
View File
@@ -116,7 +116,7 @@ export function PeopleSearch({
startAdornment: (
<>
<PeopleOutlineOutlinedIcon
sx={{ mr: 1, color: "action.active" }}
style={{ marginRight: 8, color: "rgba(0, 0, 0, 0.54)" }}
/>
{params.InputProps.startAdornment}
</>
@@ -164,7 +164,7 @@ export function PeopleSearch({
<Chip
{...getTagProps({ index })}
key={label}
sx={{
style={{
backgroundColor: chipColor,
color: textColor,
}}
-155
View File
@@ -1,155 +0,0 @@
.App {
background-color: #f3f6f9;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.main-layout {
background-color: #fff;
flex-direction: row;
height: calc(100vh - 90px);
display: flex;
}
.calendar {
flex-grow: 1;
height: 100%;
overflow: hidden;
}
.calendarListHeader {
padding-top: 10px;
display: flex;
align-items: center;
margin-left: 25%;
}
.sidebar {
border-right: 2px solid #f3f4f6;
width: 330px;
padding: 16px 25px;
height: 90vh;
flex-direction: column;
overflow: auto;
}
.declined-event {
opacity: 0.7;
}
.needs-action-event {
opacity: 0.7;
border: dashed 1px #ffffff;
}
.fc-event-main span,
.fc-daygrid-event div {
display: block !important;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
}
[data-theme="dark"] .fc-timegrid-slot::after {
border-top: 1px dotted rgba(255, 255, 255, 0.2);
}
#calendar {
flex: 1;
overflow: hidden; /* Important: let FullCalendar handle internal scroll */
}
/* Base container */
.fc-daygrid-day-top {
display: flex;
flex-direction: column;
align-items: center;
padding: 0.5rem 0;
font-family: "Inter", sans-serif;
}
/* sidebar*/
.sidebar-calendar {
width: 280px;
background-color: #fff;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 20px;
display: flex;
flex-direction: column;
gap: 30px;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
}
/* mini calendar style */
.react-calendar {
border: none;
font-size: 14px;
width: 100%;
user-select: none;
}
/* selected day hover */
.react-calendar__tile {
transition:
background-color 0.3s ease,
color 0.3s ease;
border: none;
background-color: white;
width: 12px;
height: 37.52px;
}
.react-calendar__tile--active {
background-color: rgb(255, 255, 255);
}
.today {
background: orange !important;
color: white;
}
.selectedWeek {
background-color: lightgrey;
}
.react-calendar__month-view__days__day--neighboringMonth {
color: gray;
}
/* sidebar header */
.sidebar-calendar h2 {
font-size: 1.3rem;
font-weight: 700;
color: #333;
margin: 0 0 10px 0;
}
/* Calendar list */
.calendar-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 15px;
}
.calendar-list li {
display: flex;
align-items: center;
gap: 10px;
font-weight: 600;
color: #444;
padding: 10px 15px;
border-radius: 10px;
transition: background-color 0.2s ease;
cursor: pointer;
}
.event-dot {
margin-top: 2px;
width: 6px;
height: 6px;
background-color: #d93025;
border-radius: 50%;
margin-left: auto;
margin-right: auto;
}
+135
View File
@@ -0,0 +1,135 @@
.App
background-color #f3f6f9
display flex
flex-direction column
justify-content space-between
.main-layout
background-color #fff
flex-direction row
height calc(100vh - 90px)
display flex
.calendar
flex-grow 1
height 100%
overflow hidden
.calendarListHeader
padding-top 10px
display flex
align-items center
margin-left 25%
.sidebar
border-right 2px solid #f3f4f6
width 330px
padding 16px 25px
height 90vh
flex-direction column
overflow auto
.declined-event
opacity 0.7
.needs-action-event
opacity 0.7
border dashed 1px #ffffff
.fc-event-main span,
.fc-daygrid-event div
display block !important
white-space nowrap !important
overflow hidden !important
text-overflow ellipsis !important
[data-theme="dark"] .fc-timegrid-slot::after
border-top 1px dotted rgba(255, 255, 255, 0.2)
#calendar
flex 1
overflow hidden /* Important: let FullCalendar handle internal scroll */
/* Base container */
.fc-daygrid-day-top
display flex
flex-direction column
align-items center
padding 0.5rem 0
font-family "Inter", sans-serif
/* sidebar*/
.sidebar-calendar
width 280px
background-color #fff
border-radius 12px
box-shadow 0 4px 12px rgba(0, 0, 0, 0.1)
padding 20px
display flex
flex-direction column
gap 30px
font-family "Segoe UI", Tahoma, Geneva, Verdana, sans-serif
/* mini calendar style */
.react-calendar
border none
font-size 14px
width 100%
user-select none
/* selected day hover */
.react-calendar__tile
transition background-color 0.3s ease, color 0.3s ease
border none
background-color white
width 12px
height 37.52px
.react-calendar__tile--active
background-color rgb(255, 255, 255)
.today
background orange !important
color white
.selectedWeek
background-color lightgrey
.react-calendar__month-view__days__day--neighboringMonth
color gray
/* sidebar header */
.sidebar-calendar h2
font-size 1.3rem
font-weight 700
color #333
margin 0 0 10px 0
/* Calendar list */
.calendar-list
list-style none
padding 0
margin 0
display flex
flex-direction column
gap 15px
.calendar-list li
display flex
align-items center
gap 10px
font-weight 600
color #444
padding 10px 15px
border-radius 10px
transition background-color 0.2s ease
cursor pointer
.event-dot
margin-top 2px
width 6px
height 6px
background-color #d93025
border-radius 50%
margin-left auto
margin-right auto
+2 -2
View File
@@ -4,8 +4,8 @@ import timeGridPlugin from "@fullcalendar/timegrid";
import interactionPlugin from "@fullcalendar/interaction";
import { CalendarApi, DateSelectArg } from "@fullcalendar/core";
import ReactCalendar from "react-calendar";
import "./Calendar.css";
import "./CustomCalendar.css";
import "./Calendar.styl";
import "./CustomCalendar.styl";
import { useEffect, useRef, useState } from "react";
import { useAppDispatch, useAppSelector } from "../../app/hooks";
import EventPopover from "../../features/Events/EventModal";
@@ -19,11 +19,11 @@ export function ColorPicker({
role="button"
aria-label={`select color ${c}`}
onClick={() => onChange(c)}
sx={{
style={{
width: 20,
height: 20,
borderRadius: "50%",
bgcolor: c,
backgroundColor: c,
cursor: "pointer",
border:
selectedColor === c ? "2px solid black" : "2px solid transparent",
+17 -14
View File
@@ -44,18 +44,18 @@ function CalendarItem({
display="flex"
alignItems="center"
justifyContent="space-between"
sx={{
borderRadius: 2,
style={{
borderRadius: 8,
border: "1px solid #e5e7eb",
p: 1,
mb: 1,
padding: 8,
marginBottom: 8,
}}
>
<Box display="flex" alignItems="center" gap={2}>
<Avatar
src={cal.owner.avatarUrl}
alt={cal.owner.email}
sx={{
style={{
border: `2px solid ${cal.cal["apple:color"] ?? "transparent"}`,
boxShadow: cal.cal["apple:color"]
? `0 0 0 2px ${cal.cal["apple:color"]}`
@@ -228,7 +228,7 @@ export default function CalendarSearch({
}}
>
<Box
sx={{
style={{
position: "absolute",
left: "50%",
top: "20%",
@@ -238,16 +238,16 @@ export default function CalendarSearch({
}}
>
<Card
sx={{
p: 2,
borderRadius: 3,
boxShadow: 6,
style={{
padding: 16,
borderRadius: 12,
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
display: "flex",
flexDirection: "column",
maxHeight: "80vh",
}}
>
<Box sx={{ position: "absolute", top: 8, right: 8 }}>
<Box style={{ position: "absolute", top: 8, right: 8 }}>
<IconButton
size="small"
onClick={() => onClose({}, "backdropClick")}
@@ -256,8 +256,11 @@ export default function CalendarSearch({
</IconButton>
</Box>
<CardHeader title="Browse other calendars" sx={{ pb: 0 }} />
<CardContent sx={{ flex: 1, overflow: "auto" }}>
<CardHeader
title="Browse other calendars"
style={{ paddingBottom: 0 }}
/>
<CardContent style={{ flex: 1, overflow: "auto" }}>
<PeopleSearch
objectTypes={["user"]}
selectedUsers={selectedUsers}
@@ -319,7 +322,7 @@ export default function CalendarSearch({
/>
</CardContent>
<CardActions sx={{ justifyContent: "flex-end", gap: 1 }}>
<CardActions style={{ justifyContent: "flex-end", gap: 8 }}>
<Button
variant="outlined"
onClick={() => onClose({}, "backdropClick")}
@@ -8,6 +8,8 @@ import { useState } from "react";
import CalendarPopover from "./CalendarModal";
import { Calendars } from "../../features/Calendars/CalendarTypes";
import MoreVertIcon from "@mui/icons-material/MoreVert";
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
import CalendarSearch from "./CalendarSearch";
import IconButton from "@mui/material/IconButton";
import Checkbox from "@mui/material/Checkbox";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
-238
View File
@@ -1,238 +0,0 @@
/* Custom main calendar view */
.fc-theme-standard td,
.fc-theme-standard th {
border: 1px solid #b8c1cc;
}
th.fc-col-header-cell.fc-day {
border-left: 0;
}
a.fc-timegrid-axis-cushion {
color: #243b55;
text-align: center;
font-family: Roboto;
font-size: 12.507px;
font-style: normal;
font-weight: 400;
line-height: 18.76px;
letter-spacing: 0.391px;
}
/* Slot label visibility transitions */
.fc-timegrid-slot-label {
transition: opacity 0.3s ease-in-out;
}
.fc-daygrid-day-top small {
color: #8c9caf;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 20px;
letter-spacing: 0.25px;
font-family: "Inter", sans-serif;
}
.fc-daygrid-day-top .fc-daygrid-day-number,
span.fc-daygrid-day-number {
color: #243b55;
margin: 0 6px 0 0;
font-family: Roboto;
font-size: 28px;
font-style: normal;
font-weight: 400;
line-height: 36px;
position: relative;
width: 48px;
height: 48px;
line-height: 39px;
}
.fc-daygrid-day-top .fc-daygrid-day-number:after,
span.fc-daygrid-day-number:after {
content: "";
z-index: -1;
border-radius: 50%;
width: 45px;
height: 45px;
position: absolute;
top: 1px;
left: 1px;
transition: background-color 0.3s ease;
}
span.fc-daygrid-day-number.current-date {
color: #fff;
}
span.fc-daygrid-day-number.current-date:after {
background-color: #f67e35;
}
td.fc-timegrid-slot.fc-timegrid-slot-label.fc-scrollgrid-shrink,
td.fc-timegrid-slot.fc-timegrid-slot-label.fc-timegrid-slot-minor {
border: 0;
}
th.fc-timegrid-axis.fc-scrollgrid-shrink {
border: 0;
border-right: 1px solid #b8c1cc;
}
.fc .fc-timegrid-axis-cushion {
min-width: 80px;
max-width: 80px;
padding: 0;
text-align: left;
font-family: Roboto;
font-size: 12.507px;
font-style: normal;
font-weight: 400;
line-height: 18.76px;
padding: 0 0 0 10px;
}
.fc .fc-timegrid-divider {
display: none;
height: 0;
}
.fc-timegrid-slot-label-cushion {
color: #aea9b1;
font-family: Roboto;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
letter-spacing: 0.25px;
}
th.fc-col-header-cell.fc-day {
border-right: 0;
border-bottom: 0;
}
.fc .fc-scrollgrid {
border: 0;
}
.fc .fc-timegrid-slot-label {
justify-content: flex-end;
}
.fc .fc-timegrid-slot-label .fc-timegrid-slot-label-frame {
position: relative;
transform: translate(-15px, -12px);
}
.fc
tbody
tr:first-of-type
.fc-timegrid-slot-label
.fc-timegrid-slot-label-frame {
display: none;
}
.fc .fc-timegrid-slot-label .fc-timegrid-slot-label-frame::after {
content: "";
z-index: -1;
background-color: #b8c1cc;
width: 10px;
height: 1px;
position: absolute;
top: 9.5px;
right: -15px;
}
.fc .fc-timegrid-col.fc-day-today,
.fc .fc-daygrid-day.fc-day-today {
background-color: #fff;
}
.fc .fc-daygrid-body-unbalanced .fc-daygrid-day-events {
min-height: 36px;
}
.fc .fc-daygrid-body-natural .fc-daygrid-day-events {
margin-bottom: 0;
}
.fc .fc-timegrid-now-indicator-line {
z-index: 5;
border: 0 solid #f67e35;
border-top-width: 2px;
position: absolute;
left: 0;
right: 0;
}
.fc .fc-timegrid-now-indicator-line::after {
content: "";
z-index: 5;
background-color: #f67e35;
border-radius: 50%;
width: 10px;
height: 10px;
position: absolute;
top: -6px;
left: -5px;
}
.fc .fc-timegrid-now-indicator-arrow {
display: block !important;
width: auto;
height: auto;
position: relative;
border: 0;
}
.fc .fc-timegrid-now-indicator-arrow::before {
content: attr(data-time);
color: #f67e35;
white-space: nowrap;
font-family: Roboto, sans-serif;
font-weight: 600;
position: absolute;
top: -2px;
left: 25px;
font-size: 14px;
letter-spacing: 0.25px;
}
.fc-day-today .fc-timegrid-now-indicator-container {
overflow: unset;
}
.fc .fc-timegrid-slot {
height: auto;
min-height: 20px !important;
}
.fc .fc-timegrid-slot-label {
display: flex;
align-items: flex-start;
padding-top: 2px;
height: 100%;
}
tr:has(> td.fc-timegrid-divider.fc-cell-shaded) {
display: none;
}
.fc .fc-scrollgrid-section td {
border-bottom: 0;
}
.fc .fc-scrollgrid-section td .fc-daygrid-day-frame {
border-bottom: 1px solid #b8c1cc;
}
th.fc-col-header-cell.fc-day {
cursor: pointer;
}
th.fc-col-header-cell.fc-day:hover
.fc-daygrid-day-number:not(.current-date)::after {
background-color: #b8c1cc56;
}
/* Hover effects for month view using CSS */
.fc-daygrid-day:hover {
background-color: #b8c1cc1a !important;
transition: background-color 0.3s ease;
}
/* Hover effects for week and day views using JavaScript */
.hoverable-day-cell {
transition: background-color 0.3s ease;
}
.hour-highlight {
transition: background-color 0.3s ease;
background-color: #b8c1cc1a !important;
position: absolute;
left: 0;
right: 0;
pointer-events: none;
z-index: 1;
}
.fc-timegrid-slot-label {
background-color: #fff !important;
}
thead .fc-scroller,
tbody > tr.fc-scrollgrid-section:first-of-type .fc-scroller {
overflow: hidden !important;
}
.navigation-controls {
margin-right: 40px;
}
+226
View File
@@ -0,0 +1,226 @@
/* Custom main calendar view */
.fc-theme-standard td,
.fc-theme-standard th
border 1px solid #b8c1cc
th.fc-col-header-cell.fc-day
border-left 0
a.fc-timegrid-axis-cushion
color #243b55
text-align center
font-family Roboto
font-size 12.507px
font-style normal
font-weight 400
line-height 18.76px
letter-spacing 0.391px
/* Slot label visibility transitions */
.fc-timegrid-slot-label
transition opacity 0.3s ease-in-out
.fc-daygrid-day-top small
color #8c9caf
font-size 14px
font-style normal
font-weight 500
line-height 20px
letter-spacing 0.25px
font-family "Inter", sans-serif
.fc-daygrid-day-top .fc-daygrid-day-number,
span.fc-daygrid-day-number
color #243b55
margin 0 6px 0 0
font-family Roboto
font-size 28px
font-style normal
font-weight 400
line-height 36px
position relative
width 48px
height 48px
line-height 39px
.fc-daygrid-day-top .fc-daygrid-day-number:after,
span.fc-daygrid-day-number:after
content ""
z-index -1
border-radius 50%
width 45px
height 45px
position absolute
top 1px
left 1px
transition background-color 0.3s ease
span.fc-daygrid-day-number.current-date
color #fff
span.fc-daygrid-day-number.current-date:after
background-color #f67e35
td.fc-timegrid-slot.fc-timegrid-slot-label.fc-scrollgrid-shrink,
td.fc-timegrid-slot.fc-timegrid-slot-label.fc-timegrid-slot-minor
border 0
th.fc-timegrid-axis.fc-scrollgrid-shrink
border 0
border-right 1px solid #b8c1cc
.fc .fc-timegrid-axis-cushion
min-width 80px
max-width 80px
padding 0
text-align left
font-family Roboto
font-size 12.507px
font-style normal
font-weight 400
line-height 18.76px
padding 0 0 0 10px
.fc .fc-timegrid-divider
display none
height 0
.fc-timegrid-slot-label-cushion
color #aea9b1
font-family Roboto
font-size 14px
font-style normal
font-weight 400
line-height 20px
letter-spacing 0.25px
th.fc-col-header-cell.fc-day
border-right 0
border-bottom 0
.fc .fc-scrollgrid
border 0
.fc .fc-timegrid-slot-label
justify-content flex-end
.fc .fc-timegrid-slot-label .fc-timegrid-slot-label-frame
position relative
transform translate(-15px, -12px)
/* .fc tbody tr:first-of-type .fc-timegrid-slot-label .fc-timegrid-slot-label-frame
display none */
.fc .fc-timegrid-slot-label .fc-timegrid-slot-label-frame::after
content ""
z-index -1
background-color #b8c1cc
width 10px
height 1px
position absolute
top 9.5px
right -15px
.fc .fc-timegrid-col.fc-day-today,
.fc .fc-daygrid-day.fc-day-today
background-color #fff
.fc .fc-daygrid-body-unbalanced .fc-daygrid-day-events
min-height 36px
.fc .fc-daygrid-body-natural .fc-daygrid-day-events
margin-bottom 0
.fc .fc-timegrid-now-indicator-line
z-index 5
border 0 solid #f67e35
border-top-width 2px
position absolute
left 0
right 0
.fc .fc-timegrid-now-indicator-line::after
content ""
z-index 5
background-color #f67e35
border-radius 50%
width 10px
height 10px
position absolute
top -6px
left -5px
.fc .fc-timegrid-now-indicator-arrow
display block !important
width auto
height auto
position relative
border 0
.fc .fc-timegrid-now-indicator-arrow::before
content attr(data-time)
color #f67e35
white-space nowrap
font-family Roboto, sans-serif
font-weight 600
position absolute
top -2px
left 25px
font-size 14px
letter-spacing 0.25px
.fc-day-today .fc-timegrid-now-indicator-container
overflow unset
.fc .fc-timegrid-slot
height auto
min-height 20px !important
.fc .fc-timegrid-slot-label
display flex
align-items flex-start
padding-top 2px
height 100%
tr:has(> td.fc-timegrid-divider.fc-cell-shaded)
display none
.fc .fc-scrollgrid-section td
border-bottom 0
.fc .fc-scrollgrid-section td .fc-daygrid-day-frame
border-bottom 1px solid #b8c1cc
th.fc-col-header-cell.fc-day
cursor pointer
th.fc-col-header-cell.fc-day:hover .fc-daygrid-day-number:not(.current-date)::after
background-color #b8c1cc56
/* Hover effects for month view using CSS */
.fc-daygrid-day:hover
background-color #b8c1cc1a !important
transition background-color 0.3s ease
/* Hover effects for week and day views using JavaScript */
.hoverable-day-cell
transition background-color 0.3s ease
.hour-highlight
transition background-color 0.3s ease
background-color #b8c1cc1a !important
position absolute
left 0
right 0
pointer-events none
z-index 1
.fc-timegrid-slot-label
background-color #fff !important
thead .fc-scroller,
tbody > tr.fc-scrollgrid-section:first-of-type .fc-scroller
overflow hidden !important
.navigation-controls
margin-right 40px
+3 -3
View File
@@ -98,7 +98,7 @@ export default function RepeatEvent({
})
}
size="small"
sx={{ width: 80 }}
style={{ width: 80 }}
/>
<Typography>
{
@@ -136,7 +136,7 @@ export default function RepeatEvent({
{/* End options */}
<Box>
<Typography variant="body2" gutterBottom sx={{ mt: 2 }}>
<Typography variant="body2" gutterBottom style={{ marginTop: 16 }}>
End:
</Typography>
<RadioGroup
@@ -187,7 +187,7 @@ export default function RepeatEvent({
occurrences: Number(e.target.value),
})
}
sx={{ width: 100 }}
style={{ width: 100 }}
inputProps={{ min: 1 }}
disabled={endOption !== "after"}
/>
-107
View File
@@ -1,107 +0,0 @@
/* Basic reset */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.twake {
font-family: "Cal Sans";
font-weight: 400;
font-size: 28.07px;
line-height: 100%;
letter-spacing: 0%;
text-align: center;
vertical-align: middle;
padding: 0.2rem;
}
.calendar-text {
font-family: "Cal Sans";
padding: 0.2rem;
font-weight: 400;
font-size: 28.07px;
line-height: 100%;
letter-spacing: 0%;
text-align: center;
vertical-align: middle;
background: linear-gradient(180deg, #ffb73d, #f26c32);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
/* Optional for broader support */
background-clip: text;
color: transparent;
}
/* Menubar styles */
.menubar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
width: 100%;
z-index: 1000;
height: 80px;
background-color: #fff;
}
.menubar-item {
display: flex;
align-items: center;
width: calc(330px - 0.5rem);
}
.logo {
padding: 0.5rem 1rem;
font-size: 1.5rem;
}
.nav-month {
padding-right: 2px;
padding-left: 2px;
gap: 38px;
}
.day-selector {
font-weight: 100;
font-size: small;
}
.search-bar {
width: 600;
height: 43.88999938964844;
top: 18px;
left: 887px;
}
.app-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
padding: 16px;
justify-items: center;
align-items: center;
}
.app-grid img {
width: 48px;
height: 48px;
}
.app-grid p {
margin-top: 6px;
font-size: 14px;
text-align: center;
}
.left-menu {
display: flex;
align-items: center;
justify-content: flex-start;
}
.right-menu {
display: flex;
justify-content: flex-end;
align-items: center;
}
+93
View File
@@ -0,0 +1,93 @@
/* Basic reset */
*
margin 0
padding 0
box-sizing border-box
.twake
font-family "Cal Sans"
font-weight 400
font-size 28.07px
line-height 100%
letter-spacing 0%
text-align center
vertical-align middle
padding 0.2rem
.calendar-text
font-family "Cal Sans"
padding 0.2rem
font-weight 400
font-size 28.07px
line-height 100%
letter-spacing 0%
text-align center
vertical-align middle
background linear-gradient(180deg, #ffb73d, #f26c32)
-webkit-background-clip text
-webkit-text-fill-color transparent
/* Optional for broader support */
background-clip text
color transparent
/* Menubar styles */
.menubar
display flex
justify-content space-between
align-items center
padding 0.5rem
width 100%
z-index 1000
height 80px
background-color #fff
.menubar-item
display flex
align-items center
width calc(330px - 0.5rem)
.logo
padding 0.5rem 1rem
font-size 1.5rem
.nav-month
padding-right 2px
padding-left 2px
gap 38px
.day-selector
font-weight 100
font-size small
.search-bar
width 600
height 43.88999938964844
top 18px
left 887px
.app-grid
display grid
grid-template-columns repeat(3, 1fr)
gap 16px
padding 16px
justify-items center
align-items center
.app-grid img
width 48px
height 48px
.app-grid p
margin-top 6px
font-size 14px
text-align center
.left-menu
display flex
align-items center
justify-content flex-start
.right-menu
display flex
justify-content flex-end
align-items center
+8 -5
View File
@@ -4,7 +4,7 @@ import AppsIcon from "@mui/icons-material/Apps";
import RefreshIcon from "@mui/icons-material/Refresh";
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
import "./Menubar.css";
import "./Menubar.styl";
import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { stringToColor } from "../../features/Events/EventDisplay";
import {
@@ -141,7 +141,10 @@ export function Menubar({
</IconButton>
</div>
<div className="menu-items">
<FormControl size="small" sx={{ minWidth: 120, mr: 2 }}>
<FormControl
size="small"
style={{ minWidth: 120, marginRight: 16 }}
>
<Select
value={currentView}
onChange={(e) => handleViewChange(e.target.value)}
@@ -155,15 +158,15 @@ export function Menubar({
</div>
<div className="menu-items">
{applist.length > 0 && (
<IconButton onClick={handleOpen} sx={{ mr: 1 }}>
<IconButton onClick={handleOpen} style={{ marginRight: 8 }}>
<AppsIcon />
</IconButton>
)}
</div>
<div className="menu-items">
<Avatar
sx={{
bgcolor: stringToColor(
style={{
backgroundColor: stringToColor(
user && user.family_name
? user.family_name
: user && user.email
+27 -22
View File
@@ -203,7 +203,7 @@ export default function EventDisplayModal({
<MenuItem key={index} value={index}>
<Typography variant="body2">
<CircleIcon
sx={{
style={{
color: userPersonnalCalendars[index].color ?? "#3788D8",
width: 12,
height: 12,
@@ -217,7 +217,7 @@ export default function EventDisplayModal({
<MenuItem key={index} value={index}>
<Typography variant="body2">
<CircleIcon
sx={{
style={{
color: calendars[index].color ?? "#3788D8",
width: 12,
height: 12,
@@ -231,7 +231,7 @@ export default function EventDisplayModal({
return (
<Modal open={open} onClose={onClose}>
<Box
sx={{
style={{
position: "absolute",
top: "5vh",
left: "50%",
@@ -240,9 +240,9 @@ export default function EventDisplayModal({
maxHeight: "80vh",
}}
>
<Card sx={{ p: 2, position: "absolute" }}>
<Card style={{ padding: 16, position: "absolute" }}>
{/* Close button */}
<Box sx={{ position: "absolute", top: 8, right: 8 }}>
<Box style={{ position: "absolute", top: 8, right: 8 }}>
<IconButton
size="small"
onClick={() => onClose({}, "backdropClick")}
@@ -253,7 +253,7 @@ export default function EventDisplayModal({
<CardHeader title={isOwn ? "Edit Event" : "Event Details"} />
<CardContent sx={{ maxHeight: "75vh", overflow: "auto" }}>
<CardContent style={{ maxHeight: "75vh", overflow: "auto" }}>
{/* Title */}
<TextField
fullWidth
@@ -267,7 +267,7 @@ export default function EventDisplayModal({
{/* RSVP */}
{currentUserAttendee && isOwnCal && (
<Card sx={{ my: 1 }}>
<Card style={{ margin: "8px 0" }}>
<ButtonGroup size="small" fullWidth>
<Button
color={
@@ -408,7 +408,7 @@ export default function EventDisplayModal({
{/* Video */}
{event.x_openpass_videoconference && (
<InfoRow
icon={<VideocamIcon sx={{ fontSize: 18 }} />}
icon={<VideocamIcon style={{ fontSize: 18 }} />}
text="Video conference available"
data={event.x_openpass_videoconference}
/>
@@ -416,7 +416,7 @@ export default function EventDisplayModal({
{/* Attendees */}
{event.attendee?.length > 0 && (
<Box sx={{ mb: 1 }}>
<Box style={{ marginBottom: 8 }}>
<Typography variant="subtitle2">Attendees:</Typography>
{organizer.cal_address &&
renderAttendeeBadge(organizer, "org", true)}
@@ -444,7 +444,7 @@ export default function EventDisplayModal({
<Typography
variant="body2"
color="primary"
sx={{ cursor: "pointer", mt: 0.5 }}
style={{ cursor: "pointer", marginTop: 4 }}
onClick={() => setShowAllAttendees(!showAllAttendees)}
>
{showAllAttendees
@@ -457,7 +457,7 @@ export default function EventDisplayModal({
</Box>
)}
<Divider sx={{ my: 1 }} />
<Divider style={{ margin: "8px 0" }} />
{/* Extended options */}
{showMore && (
@@ -527,7 +527,10 @@ export default function EventDisplayModal({
{event.error && (
<InfoRow
icon={
<ErrorOutlineIcon color="error" sx={{ fontSize: 18 }} />
<ErrorOutlineIcon
color="error"
style={{ fontSize: 18 }}
/>
}
text={event.error}
error
@@ -581,7 +584,9 @@ export function InfoRow({
data?: string;
}) {
return (
<Box sx={{ display: "flex", alignItems: "center", gap: 1, mb: 1 }}>
<Box
style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 8 }}
>
{icon}
<Typography variant="body2" color={error ? "error" : "textPrimary"}>
{isValidUrl(data) ? <Link href={data}>{text}</Link> : text}
@@ -605,13 +610,13 @@ export function renderAttendeeBadge(
return (
<Box
key={key}
sx={{
style={{
display: "flex",
alignItems: "center",
gap: 1.5,
mb: 0.5,
p: 0.5,
borderRadius: 1,
gap: 12,
marginBottom: 4,
padding: 4,
borderRadius: 4,
}}
>
<Badge
@@ -620,7 +625,7 @@ export function renderAttendeeBadge(
badgeContent={
classIcon && (
<Box
sx={{
style={{
fontSize: 14,
lineHeight: 0,
backgroundColor: "white",
@@ -635,11 +640,11 @@ export function renderAttendeeBadge(
>
<Avatar {...stringAvatar(a.cn || a.cal_address)} />
</Badge>
<Box sx={{ display: "flex", flexDirection: "column", minWidth: 0 }}>
<Box style={{ display: "flex", flexDirection: "column", minWidth: 0 }}>
<Typography
variant="body2"
noWrap
sx={{
style={{
maxWidth: "180px",
overflow: "hidden",
textOverflow: "ellipsis",
@@ -651,7 +656,7 @@ export function renderAttendeeBadge(
<Typography
variant="caption"
color="text.secondary"
sx={{ fontStyle: "italic" }}
style={{ fontStyle: "italic" }}
>
Organizer
</Typography>
+25 -16
View File
@@ -115,15 +115,15 @@ export default function EventPreviewModal({
anchorPosition={anchorPosition ?? undefined}
onClose={onClose}
>
<Card sx={{ width: 300, p: 2, position: "relative" }}>
<Card style={{ width: 300, padding: 16, position: "relative" }}>
{/* Top-right buttons */}
<Box
sx={{
style={{
position: "absolute",
top: 8,
right: 8,
display: "flex",
gap: 1,
gap: 8,
}}
>
{(window as any).DEBUG && (
@@ -205,12 +205,12 @@ export default function EventPreviewModal({
</IconButton>
</Box>
<CardContent sx={{ pt: 1.5 }}>
<CardContent style={{ paddingTop: 12 }}>
{event.title && (
<Typography
variant="h6"
fontWeight="bold"
sx={{
style={{
wordBreak: "break-word",
}}
gutterBottom
@@ -230,7 +230,7 @@ export default function EventPreviewModal({
{/* Location */}
{event.location && (
<InfoRow
icon={<LocationOnIcon sx={{ fontSize: 18 }} />}
icon={<LocationOnIcon style={{ fontSize: 18 }} />}
text={event.location}
/>
)}
@@ -238,7 +238,7 @@ export default function EventPreviewModal({
{/* Video */}
{event.x_openpass_videoconference && (
<InfoRow
icon={<VideocamIcon sx={{ fontSize: 18 }} />}
icon={<VideocamIcon style={{ fontSize: 18 }} />}
text="Video conference available"
data={event.x_openpass_videoconference}
/>
@@ -246,7 +246,7 @@ export default function EventPreviewModal({
{/* Attendees */}
{event.attendee?.length > 0 && (
<Box sx={{ mb: 1 }}>
<Box style={{ marginBottom: 8 }}>
<Typography variant="subtitle2">Attendees:</Typography>
{organizer && renderAttendeeBadge(organizer, "org", true)}
{visibleAttendees.map((a, idx) =>
@@ -256,7 +256,7 @@ export default function EventPreviewModal({
<Typography
variant="body2"
color="primary"
sx={{ cursor: "pointer", mt: 0.5 }}
style={{ cursor: "pointer", marginTop: 4 }}
onClick={() => setShowAllAttendees(!showAllAttendees)}
>
{showAllAttendees
@@ -272,17 +272,26 @@ export default function EventPreviewModal({
{/* Error */}
{event.error && (
<InfoRow
icon={<ErrorOutlineIcon color="error" sx={{ fontSize: 18 }} />}
icon={
<ErrorOutlineIcon color="error" style={{ fontSize: 18 }} />
}
text={event.error}
error
/>
)}
{/* Calendar color dot */}
<Box sx={{ display: "flex", alignItems: "center", gap: 1, mb: 2 }}>
<CalendarTodayIcon sx={{ fontSize: 16 }} />
<Box
style={{
display: "flex",
alignItems: "center",
gap: 8,
marginBottom: 16,
}}
>
<CalendarTodayIcon style={{ fontSize: 16 }} />
<CircleIcon
sx={{
style={{
color: calendar.color ?? "#3788D8",
width: 12,
height: 12,
@@ -291,12 +300,12 @@ export default function EventPreviewModal({
<Typography variant="body2">{calendar.name}</Typography>
</Box>
<Divider sx={{ mb: 1 }} />
<Divider style={{ marginBottom: 8 }} />
{/* RSVP */}
{currentUserAttendee && (
<Box>
<Typography variant="body2" sx={{ mb: 1 }}>
<Typography variant="body2" style={{ marginBottom: 8 }}>
Will you attend?
</Typography>
<ButtonGroup size="small" fullWidth>
@@ -336,7 +345,7 @@ export default function EventPreviewModal({
{/* Description */}
{event.description && (
<Typography variant="body2" sx={{ mt: 1 }}>
<Typography variant="body2" style={{ marginTop: 8 }}>
{event.description}
</Typography>
)}
+1 -1
View File
@@ -190,7 +190,7 @@ function EventPopover({
<Card>
<CardHeader title={event?.uid ? "Duplicate Event" : "Create Event"} />
<CardContent
sx={{ maxHeight: "85vh", maxWidth: "40vw", overflow: "auto" }}
style={{ maxHeight: "85vh", maxWidth: "40vw", overflow: "auto" }}
>
<TextField
fullWidth
-13
View File
@@ -1,13 +0,0 @@
body {
margin: 0;
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu",
"Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family:
source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
}
+8
View File
@@ -0,0 +1,8 @@
body
margin 0
font-family -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
code
font-family source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace