Merge branch 'Longhorn-Developers:main' into master
This commit is contained in:
4
.github/workflows/best-practices.yml
vendored
4
.github/workflows/best-practices.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v3
|
uses: pnpm/action-setup@v3
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v3
|
uses: pnpm/action-setup@v3
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|||||||
2
.github/workflows/check-types.yml
vendored
2
.github/workflows/check-types.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v3
|
uses: pnpm/action-setup@v3
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|||||||
2
.github/workflows/chromatic.yml
vendored
2
.github/workflows/chromatic.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v3
|
uses: pnpm/action-setup@v3
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|||||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v3
|
uses: pnpm/action-setup@v3
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|||||||
@@ -2,7 +2,13 @@ import type { StorybookConfig } from '@storybook/react-vite';
|
|||||||
|
|
||||||
const config: StorybookConfig = {
|
const config: StorybookConfig = {
|
||||||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||||
addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-designs'],
|
addons: [
|
||||||
|
'@storybook/addon-links',
|
||||||
|
'@storybook/addon-essentials',
|
||||||
|
'@storybook/addon-designs',
|
||||||
|
'@storybook/test',
|
||||||
|
'@chromatic-com/storybook',
|
||||||
|
],
|
||||||
framework: {
|
framework: {
|
||||||
name: '@storybook/react-vite',
|
name: '@storybook/react-vite',
|
||||||
options: {
|
options: {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
||||||
import type { Preview } from '@storybook/react';
|
import type { Preview } from '@storybook/react';
|
||||||
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
|
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const preview: Preview = {
|
const preview: Preview = {
|
||||||
parameters: {
|
parameters: {
|
||||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
|
||||||
controls: {
|
controls: {
|
||||||
matchers: {
|
matchers: {
|
||||||
color: /(background|color)$/i,
|
color: /(background|color)$/i,
|
||||||
@@ -166,4 +166,13 @@ globalThis.chrome = {
|
|||||||
},
|
},
|
||||||
} as typeof chrome;
|
} as typeof chrome;
|
||||||
|
|
||||||
|
// set updatedAt dates to be fixed
|
||||||
|
|
||||||
|
UserScheduleStore.get('schedules').then(schedules => {
|
||||||
|
schedules.forEach(schedule => {
|
||||||
|
schedule.updatedAt = new Date('2024-01-01 12:00').getTime();
|
||||||
|
});
|
||||||
|
UserScheduleStore.set('schedules', schedules);
|
||||||
|
});
|
||||||
|
|
||||||
export default preview;
|
export default preview;
|
||||||
|
|||||||
5
chromatic.config.json
Normal file
5
chromatic.config.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"onlyChanged": true,
|
||||||
|
"projectId": "Project:65c5172964f36dcf207985bf",
|
||||||
|
"zip": true
|
||||||
|
}
|
||||||
37
package.json
37
package.json
@@ -24,7 +24,7 @@
|
|||||||
"prepare": "husky"
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^1.7.18",
|
"@headlessui/react": "^2.0.3",
|
||||||
"@hello-pangea/dnd": "^16.5.0",
|
"@hello-pangea/dnd": "^16.5.0",
|
||||||
"@unocss/vite": "^0.58.6",
|
"@unocss/vite": "^0.58.6",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
@@ -35,33 +35,34 @@
|
|||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
"husky": "^9.0.11",
|
"husky": "^9.0.11",
|
||||||
"nanoid": "^5.0.6",
|
"nanoid": "^5.0.6",
|
||||||
"react": "^18.2.0",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.3.1",
|
||||||
"sass": "^1.71.1",
|
"sass": "^1.71.1",
|
||||||
"sql.js": "1.10.2"
|
"sql.js": "1.10.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@chromatic-com/storybook": "^1.4.0",
|
||||||
"@commitlint/cli": "^18.6.1",
|
"@commitlint/cli": "^18.6.1",
|
||||||
"@commitlint/config-conventional": "^18.6.2",
|
"@commitlint/config-conventional": "^18.6.2",
|
||||||
"@commitlint/types": "^19.0.3",
|
"@commitlint/types": "^19.0.3",
|
||||||
"@crxjs/vite-plugin": "2.0.0-beta.21",
|
"@crxjs/vite-plugin": "2.0.0-beta.21",
|
||||||
|
"@iconify-json/bi": "^1.1.23",
|
||||||
"@iconify-json/material-symbols": "^1.1.73",
|
"@iconify-json/material-symbols": "^1.1.73",
|
||||||
"@iconify-json/ri": "^1.1.20",
|
"@iconify-json/ri": "^1.1.20",
|
||||||
"@iconify-json/bi": "^1.1.23",
|
"@storybook/addon-designs": "^8.0.1",
|
||||||
"@storybook/addon-designs": "^7.0.9",
|
"@storybook/addon-essentials": "^8.1.1",
|
||||||
"@storybook/addon-essentials": "^7.6.17",
|
"@storybook/addon-links": "^8.1.1",
|
||||||
"@storybook/addon-links": "^7.6.17",
|
"@storybook/blocks": "^8.1.1",
|
||||||
"@storybook/blocks": "^7.6.17",
|
"@storybook/react": "^8.1.1",
|
||||||
"@storybook/react": "^7.6.17",
|
"@storybook/react-vite": "^8.1.1",
|
||||||
"@storybook/react-vite": "^7.6.17",
|
"@storybook/test": "^8.1.1",
|
||||||
"@storybook/test": "^7.6.17",
|
|
||||||
"@svgr/core": "^8.1.0",
|
"@svgr/core": "^8.1.0",
|
||||||
"@svgr/plugin-jsx": "^8.1.0",
|
"@svgr/plugin-jsx": "^8.1.0",
|
||||||
"@types/chrome": "^0.0.260",
|
"@types/chrome": "^0.0.268",
|
||||||
"@types/node": "^20.11.24",
|
"@types/node": "^20.12.12",
|
||||||
"@types/prompts": "^2.4.9",
|
"@types/prompts": "^2.4.9",
|
||||||
"@types/react": "^18.2.61",
|
"@types/react": "^18.3.2",
|
||||||
"@types/react-dom": "^18.2.19",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@types/semver": "^7.5.8",
|
"@types/semver": "^7.5.8",
|
||||||
"@types/sql.js": "^1.4.9",
|
"@types/sql.js": "^1.4.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||||
@@ -76,7 +77,7 @@
|
|||||||
"@vitejs/plugin-react-swc": "^3.6.0",
|
"@vitejs/plugin-react-swc": "^3.6.0",
|
||||||
"@vitest/coverage-v8": "^1.3.1",
|
"@vitest/coverage-v8": "^1.3.1",
|
||||||
"@vitest/ui": "^1.3.1",
|
"@vitest/ui": "^1.3.1",
|
||||||
"chromatic": "^10.9.6",
|
"chromatic": "^11.3.5",
|
||||||
"cssnano": "^6.0.5",
|
"cssnano": "^6.0.5",
|
||||||
"cssnano-preset-advanced": "^6.0.5",
|
"cssnano-preset-advanced": "^6.0.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
@@ -96,12 +97,12 @@
|
|||||||
"eslint-plugin-react-prefer-function-component": "^3.3.0",
|
"eslint-plugin-react-prefer-function-component": "^3.3.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.5",
|
"eslint-plugin-react-refresh": "^0.4.5",
|
||||||
"eslint-plugin-simple-import-sort": "^12.0.0",
|
"eslint-plugin-simple-import-sort": "^12.0.0",
|
||||||
"eslint-plugin-storybook": "^0.6.15",
|
"eslint-plugin-storybook": "^0.8.0",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"postcss": "^8.4.35",
|
"postcss": "^8.4.35",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"react-dev-utils": "^12.0.1",
|
"react-dev-utils": "^12.0.1",
|
||||||
"storybook": "^7.6.17",
|
"storybook": "^8.1.1",
|
||||||
"typescript": "^5.4.3",
|
"typescript": "^5.4.3",
|
||||||
"unocss": "^0.58.6",
|
"unocss": "^0.58.6",
|
||||||
"unocss-preset-primitives": "0.0.2-beta.0",
|
"unocss-preset-primitives": "0.0.2-beta.0",
|
||||||
|
|||||||
16641
pnpm-lock.yaml
generated
16641
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
169
src/stories/components/DialogProvider.stories.tsx
Normal file
169
src/stories/components/DialogProvider.stories.tsx
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { Button } from '@views/components/common/Button';
|
||||||
|
import DialogProvider, { usePrompt } from '@views/components/common/DialogProvider/DialogProvider';
|
||||||
|
import Text from '@views/components/common/Text/Text';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import MaterialSymbolsExpandAllRoundedIcon from '~icons/material-symbols/expand-all-rounded';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Common/DialogProvider',
|
||||||
|
component: DialogProvider,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
args: {},
|
||||||
|
argTypes: {},
|
||||||
|
} satisfies Meta<typeof DialogProvider>;
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: { children: undefined },
|
||||||
|
render: () => (
|
||||||
|
<DialogProvider>
|
||||||
|
<InnerComponent />
|
||||||
|
</DialogProvider>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const InnerComponent = () => {
|
||||||
|
const showDialog = usePrompt();
|
||||||
|
|
||||||
|
const myShow = () => {
|
||||||
|
showDialog({
|
||||||
|
title: 'Dialog Title',
|
||||||
|
description: 'Dialog Description',
|
||||||
|
// eslint-disable-next-line react/no-unstable-nested-components
|
||||||
|
buttons: close => (
|
||||||
|
<Button variant='filled' color='ut-burntorange' onClick={close}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button variant='filled' color='ut-burntorange' icon={MaterialSymbolsExpandAllRoundedIcon} onClick={myShow}>
|
||||||
|
Open Dialog
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FiveDialogs: Story = {
|
||||||
|
args: { children: undefined },
|
||||||
|
render: () => (
|
||||||
|
<DialogProvider>
|
||||||
|
<Text variant='p'>They'll open with 100ms delay</Text>
|
||||||
|
<FiveDialogsInnerComponent />
|
||||||
|
</DialogProvider>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const FiveDialogsInnerComponent = () => {
|
||||||
|
const showDialog = usePrompt();
|
||||||
|
|
||||||
|
const myShow = () => {
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
setTimeout(
|
||||||
|
() =>
|
||||||
|
showDialog({
|
||||||
|
title: `Dialog #${i}`,
|
||||||
|
description:
|
||||||
|
'Deleting Main Schedule is permanent and will remove all added courses from that schedule.',
|
||||||
|
// eslint-disable-next-line react/no-unstable-nested-components
|
||||||
|
buttons: close => (
|
||||||
|
<Button variant='filled' color='ut-burntorange' onClick={close}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
100 * i
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button variant='filled' color='ut-burntorange' icon={MaterialSymbolsExpandAllRoundedIcon} onClick={myShow}>
|
||||||
|
Open Dialogs
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NestedDialogs: Story = {
|
||||||
|
args: { children: undefined },
|
||||||
|
render: () => (
|
||||||
|
<DialogProvider>
|
||||||
|
<NestedDialogsInnerComponent />
|
||||||
|
</DialogProvider>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const NestedDialogsInnerComponent = () => {
|
||||||
|
const showDialog = usePrompt();
|
||||||
|
|
||||||
|
const myShow = () => {
|
||||||
|
showDialog({
|
||||||
|
title: 'Dialog Title',
|
||||||
|
description: 'Dialog Description',
|
||||||
|
// eslint-disable-next-line react/no-unstable-nested-components
|
||||||
|
buttons: close => (
|
||||||
|
<>
|
||||||
|
<NestedDialogsInnerComponent />
|
||||||
|
<Button variant='filled' color='ut-burntorange' onClick={close}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button variant='filled' color='ut-burntorange' icon={MaterialSymbolsExpandAllRoundedIcon} onClick={myShow}>
|
||||||
|
Open Next Dialog
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DialogWithOnClose: Story = {
|
||||||
|
args: { children: undefined },
|
||||||
|
render: () => (
|
||||||
|
<DialogProvider>
|
||||||
|
<DialogWithOnCloseInnerComponent />
|
||||||
|
</DialogProvider>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const DialogWithOnCloseInnerComponent = () => {
|
||||||
|
const showDialog = usePrompt();
|
||||||
|
const [timesClosed, setTimesClosed] = useState(0);
|
||||||
|
|
||||||
|
const myShow = () => {
|
||||||
|
showDialog({
|
||||||
|
title: 'Dialog Title',
|
||||||
|
description: 'Dialog Description',
|
||||||
|
// eslint-disable-next-line react/no-unstable-nested-components
|
||||||
|
buttons: close => (
|
||||||
|
<Button variant='filled' color='ut-burntorange' onClick={close}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
onClose: () => {
|
||||||
|
setTimesClosed(prev => prev + 1);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>
|
||||||
|
You closed the button below {timesClosed} {timesClosed === 1 ? 'time' : 'times'}
|
||||||
|
</h1>
|
||||||
|
<Button variant='filled' color='ut-burntorange' icon={MaterialSymbolsExpandAllRoundedIcon} onClick={myShow}>
|
||||||
|
Open Dialog
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -38,23 +38,9 @@ const meta: Meta<typeof ScheduleDropdown> = {
|
|||||||
layout: 'centered',
|
layout: 'centered',
|
||||||
},
|
},
|
||||||
tags: ['autodocs'],
|
tags: ['autodocs'],
|
||||||
argTypes: {
|
|
||||||
defaultOpen: {
|
|
||||||
control: {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
children: {
|
|
||||||
control: {
|
|
||||||
type: 'node',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
render: (args: ScheduleDropdownProps) => {
|
render: (args: ScheduleDropdownProps) => {
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
const [activeSchedule, schedules] = useSchedules();
|
const [activeSchedule, schedules] = useSchedules();
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(activeSchedule);
|
console.log(activeSchedule);
|
||||||
}, [activeSchedule]);
|
}, [activeSchedule]);
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ export const Default: Story = {
|
|||||||
children: generateCourseBlocks,
|
children: generateCourseBlocks,
|
||||||
itemKey: item => item.uniqueId,
|
itemKey: item => item.uniqueId,
|
||||||
gap: 12,
|
gap: 12,
|
||||||
|
onReordered: () => {},
|
||||||
},
|
},
|
||||||
render: args => (
|
render: args => (
|
||||||
<div className='w-sm'>
|
<div className='w-sm'>
|
||||||
|
|||||||
@@ -12,13 +12,6 @@ const meta = {
|
|||||||
parameters: {
|
parameters: {
|
||||||
layout: 'centered',
|
layout: 'centered',
|
||||||
},
|
},
|
||||||
argTypes: {
|
|
||||||
schedule: {
|
|
||||||
control: {
|
|
||||||
type: 'UserSchedule',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
args: {
|
args: {
|
||||||
schedule: exampleSchedule,
|
schedule: exampleSchedule,
|
||||||
},
|
},
|
||||||
@@ -30,7 +23,6 @@ type Story = StoryObj<typeof meta>;
|
|||||||
|
|
||||||
export const Active: Story = {
|
export const Active: Story = {
|
||||||
render(args) {
|
render(args) {
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
const [activeSchedule] = useSchedules();
|
const [activeSchedule] = useSchedules();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ export const Default: Story = {
|
|||||||
export const Empty: Story = {
|
export const Empty: Story = {
|
||||||
args: {
|
args: {
|
||||||
courseCells: [],
|
courseCells: [],
|
||||||
|
setCourse: () => {},
|
||||||
},
|
},
|
||||||
render: props => (
|
render: props => (
|
||||||
<div className='outline-red outline w-292.5!'>
|
<div className='outline-red outline w-292.5!'>
|
||||||
|
|||||||
@@ -135,5 +135,6 @@ export const Default: Story = {
|
|||||||
args: {
|
args: {
|
||||||
saturdayClass: true,
|
saturdayClass: true,
|
||||||
courseCells: testData,
|
courseCells: testData,
|
||||||
|
setCourse: () => {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ const meta = {
|
|||||||
},
|
},
|
||||||
tags: ['autodocs'],
|
tags: ['autodocs'],
|
||||||
render(args) {
|
render(args) {
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
const [isOpen, setIsOpen] = useState(args.open);
|
const [isOpen, setIsOpen] = useState(args.open);
|
||||||
|
|
||||||
return <CourseCatalogInjectedPopup {...args} open={isOpen} onClose={() => setIsOpen(false)} />;
|
return <CourseCatalogInjectedPopup {...args} open={isOpen} onClose={() => setIsOpen(false)} />;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"extends": "../tsconfig.json",
|
"extends": "../tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"lib": ["DOM", "es2021"],
|
"lib": ["DOM", "ESNext"],
|
||||||
"types": ["chrome", "node"]
|
"types": ["chrome", "node"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import CalendarHeader from '@views/components/calendar/CalenderHeader';
|
|||||||
import ImportantLinks from '@views/components/calendar/ImportantLinks';
|
import ImportantLinks from '@views/components/calendar/ImportantLinks';
|
||||||
import Divider from '@views/components/common/Divider';
|
import Divider from '@views/components/common/Divider';
|
||||||
import CourseCatalogInjectedPopup from '@views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup';
|
import CourseCatalogInjectedPopup from '@views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup';
|
||||||
|
import { CalendarContext } from '@views/contexts/CalendarContext';
|
||||||
import { useFlattenedCourseSchedule } from '@views/hooks/useFlattenedCourseSchedule';
|
import { useFlattenedCourseSchedule } from '@views/hooks/useFlattenedCourseSchedule';
|
||||||
import { MessageListener } from 'chrome-extension-toolkit';
|
import { MessageListener } from 'chrome-extension-toolkit';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
@@ -64,6 +65,7 @@ export default function Calendar(): JSX.Element {
|
|||||||
}, [course]);
|
}, [course]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<CalendarContext.Provider value>
|
||||||
<div className='h-full w-full flex flex-col'>
|
<div className='h-full w-full flex flex-col'>
|
||||||
<CalendarHeader
|
<CalendarHeader
|
||||||
onSidebarToggle={() => {
|
onSidebarToggle={() => {
|
||||||
@@ -72,8 +74,8 @@ export default function Calendar(): JSX.Element {
|
|||||||
/>
|
/>
|
||||||
<div className='h-full flex overflow-auto pl-3'>
|
<div className='h-full flex overflow-auto pl-3'>
|
||||||
{showSidebar && (
|
{showSidebar && (
|
||||||
<div className='h-full flex flex-none flex-col justify-between pb-5 pl-4.5 screenshot:hidden'>
|
<div className='h-full flex flex-none flex-col justify-between pb-5 screenshot:hidden'>
|
||||||
<div className='mb-3 h-full w-fit flex flex-col overflow-auto pb-2 pr-4 pt-5'>
|
<div className='mb-3 h-full w-fit flex flex-col overflow-auto pb-2 pl-4.5 pr-4 pt-5'>
|
||||||
<CalendarSchedules />
|
<CalendarSchedules />
|
||||||
<Divider orientation='horizontal' size='100%' className='my-5' />
|
<Divider orientation='horizontal' size='100%' className='my-5' />
|
||||||
<ImportantLinks />
|
<ImportantLinks />
|
||||||
@@ -83,7 +85,7 @@ export default function Calendar(): JSX.Element {
|
|||||||
<CalendarFooter />
|
<CalendarFooter />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className='h-full min-w-4xl flex flex-grow flex-col overflow-y-auto'>
|
<div className='h-full min-w-5xl flex flex-grow flex-col overflow-y-auto'>
|
||||||
<div className='min-h-2xl flex-grow overflow-auto pl-2 pr-4 pt-6 screenshot:min-h-xl'>
|
<div className='min-h-2xl flex-grow overflow-auto pl-2 pr-4 pt-6 screenshot:min-h-xl'>
|
||||||
<CalendarGrid courseCells={courseCells} setCourse={setCourse} />
|
<CalendarGrid courseCells={courseCells} setCourse={setCourse} />
|
||||||
</div>
|
</div>
|
||||||
@@ -98,5 +100,6 @@ export default function Calendar(): JSX.Element {
|
|||||||
afterLeave={() => setCourse(null)}
|
afterLeave={() => setCourse(null)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</CalendarContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,15 +12,15 @@ import Link from '../common/Link';
|
|||||||
*/
|
*/
|
||||||
export default function CalendarFooter(): JSX.Element {
|
export default function CalendarFooter(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<footer className='min-w-full w-0 space-y-2'>
|
<footer className='min-w-full w-0 pl-4.5 space-y-2'>
|
||||||
<div className='flex gap-2'>
|
<div className='flex gap-2'>
|
||||||
<Link className='linkanimate' href='#'>
|
<Link className='linkanimate' href='https://www.instagram.com/longhorndevelopers'>
|
||||||
<InstagramIcon className='h-6 w-6' />
|
<InstagramIcon className='h-6 w-6' />
|
||||||
</Link>
|
</Link>
|
||||||
<Link className='linkanimate' href='https://discord.gg/bVh9g6VFwB'>
|
<Link className='linkanimate' href='https://discord.gg/7pQDBGdmb7'>
|
||||||
<DiscordIcon className='h-6 w-6' />
|
<DiscordIcon className='h-6 w-6' />
|
||||||
</Link>
|
</Link>
|
||||||
<Link className='linkanimate' href='https://github.com/Longhorn-Developers/UT-Registration-Plus'>
|
<Link className='linkanimate' href='https://github.com/Longhorn-Developers'>
|
||||||
<GithubIcon className='h-6 w-6' />
|
<GithubIcon className='h-6 w-6' />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default function CalendarHeader({ onSidebarToggle }: CalendarHeaderProps)
|
|||||||
const [activeSchedule] = useSchedules();
|
const [activeSchedule] = useSchedules();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex items-center gap-5 border-b border-ut-offwhite px-7 py-4'>
|
<div className='flex items-center gap-5 overflow-x-auto overflow-y-hidden border-b border-ut-offwhite px-7 py-4 md:overflow-x-hidden'>
|
||||||
<Button
|
<Button
|
||||||
variant='single'
|
variant='single'
|
||||||
icon={MenuIcon}
|
icon={MenuIcon}
|
||||||
@@ -51,7 +51,7 @@ export default function CalendarHeader({ onSidebarToggle }: CalendarHeaderProps)
|
|||||||
totalCourses={activeSchedule.courses.length}
|
totalCourses={activeSchedule.courses.length}
|
||||||
/>
|
/>
|
||||||
<div className='flex items-center gap-1 screenshot:hidden'>
|
<div className='flex items-center gap-1 screenshot:hidden'>
|
||||||
<Text variant='mini' className='text-ut-gray font-normal'>
|
<Text variant='mini' className='text-nowrap text-ut-gray font-normal'>
|
||||||
DATA LAST UPDATED: {getUpdatedAtDateTimeString(activeSchedule.updatedAt)}
|
DATA LAST UPDATED: {getUpdatedAtDateTimeString(activeSchedule.updatedAt)}
|
||||||
</Text>
|
</Text>
|
||||||
<button className='inline-block h-4 w-4 bg-transparent p-0 btn'>
|
<button className='inline-block h-4 w-4 bg-transparent p-0 btn'>
|
||||||
|
|||||||
@@ -14,17 +14,17 @@ interface LinkItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const links: LinkItem[] = [
|
const links: LinkItem[] = [
|
||||||
{
|
// {
|
||||||
text: 'Feedback Form',
|
// text: 'Feedback Form',
|
||||||
url: '#',
|
// url: '#',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
text: 'Apply to Longhorn Developers',
|
// text: 'Apply to Longhorn Developers',
|
||||||
url: '#',
|
// url: '#',
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
text: 'Become a Beta Tester',
|
text: 'Become a Beta Tester',
|
||||||
url: 'https://discord.gg/bVh9g6VFwB',
|
url: 'https://forms.gle/Y9dmQAb1yzW5PRg48',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import type { TransitionRootProps } from '@headlessui/react';
|
import type { TransitionRootProps } from '@headlessui/react';
|
||||||
import { Dialog as HDialog, Transition } from '@headlessui/react';
|
import {
|
||||||
|
Description,
|
||||||
|
Dialog as HDialog,
|
||||||
|
DialogPanel,
|
||||||
|
DialogTitle,
|
||||||
|
Transition,
|
||||||
|
TransitionChild,
|
||||||
|
} from '@headlessui/react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
@@ -26,7 +33,7 @@ export default function Dialog(props: PropsWithChildren<DialogProps>): JSX.Eleme
|
|||||||
return (
|
return (
|
||||||
<Transition show={open} as={HDialog} {...rest}>
|
<Transition show={open} as={HDialog} {...rest}>
|
||||||
<ExtensionRoot>
|
<ExtensionRoot>
|
||||||
<Transition.Child
|
<TransitionChild
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter='transition duration-300 motion-reduce:duration-150 ease-out'
|
enter='transition duration-300 motion-reduce:duration-150 ease-out'
|
||||||
enterFrom='opacity-0'
|
enterFrom='opacity-0'
|
||||||
@@ -36,8 +43,8 @@ export default function Dialog(props: PropsWithChildren<DialogProps>): JSX.Eleme
|
|||||||
leaveTo='opacity-0'
|
leaveTo='opacity-0'
|
||||||
>
|
>
|
||||||
<div className={clsx('fixed inset-0 z-50 bg-slate-700/35')} />
|
<div className={clsx('fixed inset-0 z-50 bg-slate-700/35')} />
|
||||||
</Transition.Child>
|
</TransitionChild>
|
||||||
<Transition.Child
|
<TransitionChild
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter='transition duration-375 motion-reduce:duration-0 ease-[cubic-bezier(0.05,0.4,0.2,1)]'
|
enter='transition duration-375 motion-reduce:duration-0 ease-[cubic-bezier(0.05,0.4,0.2,1)]'
|
||||||
enterFrom='transform-gpu scale-95 opacity-0'
|
enterFrom='transform-gpu scale-95 opacity-0'
|
||||||
@@ -47,18 +54,18 @@ export default function Dialog(props: PropsWithChildren<DialogProps>): JSX.Eleme
|
|||||||
leaveTo='transform-gpu scale-95 opacity-0'
|
leaveTo='transform-gpu scale-95 opacity-0'
|
||||||
>
|
>
|
||||||
<div className='fixed inset-0 z-50 flex items-center justify-center'>
|
<div className='fixed inset-0 z-50 flex items-center justify-center'>
|
||||||
<HDialog.Panel
|
<DialogPanel
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'z-99 max-h-[90vh] flex flex-col overflow-y-auto border border-solid border-ut-offwhite rounded bg-white shadow-xl ml-[calc(100vw-100%)] mt-[calc(100vw-100%)]',
|
'z-99 max-h-[90vh] flex flex-col overflow-y-auto border border-solid border-ut-offwhite rounded bg-white shadow-xl ml-[calc(100vw-100%)] mt-[calc(100vw-100%)]',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{props.title && <HDialog.Title>{props.title}</HDialog.Title>}
|
{props.title && <DialogTitle as={Fragment}>{props.title}</DialogTitle>}
|
||||||
{props.description && <HDialog.Description>{props.description}</HDialog.Description>}
|
{props.description && <Description as={Fragment}>{props.description}</Description>}
|
||||||
{children}
|
{children}
|
||||||
</HDialog.Panel>
|
</DialogPanel>
|
||||||
</div>
|
</div>
|
||||||
</Transition.Child>
|
</TransitionChild>
|
||||||
</ExtensionRoot>
|
</ExtensionRoot>
|
||||||
</Transition>
|
</Transition>
|
||||||
);
|
);
|
||||||
|
|||||||
115
src/views/components/common/DialogProvider/DialogProvider.tsx
Normal file
115
src/views/components/common/DialogProvider/DialogProvider.tsx
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import type { CloseWrapper, DialogInfo, ShowDialogFn } from '@views/contexts/DialogContext';
|
||||||
|
import { DialogContext, useDialog } from '@views/contexts/DialogContext';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import React, { useCallback, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import Dialog from '../Dialog';
|
||||||
|
import Text from '../Text/Text';
|
||||||
|
|
||||||
|
type DialogElement = (show: boolean) => ReactNode;
|
||||||
|
export interface PromptInfo extends Omit<DialogInfo, 'buttons' | 'className' | 'title' | 'description'> {
|
||||||
|
title: JSX.Element | string;
|
||||||
|
description: JSX.Element | string;
|
||||||
|
onClose?: () => void;
|
||||||
|
buttons: NonNullable<DialogInfo['buttons']>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function unwrapCloseWrapper<T>(obj: T | CloseWrapper<T>, close: () => void): T {
|
||||||
|
if (typeof obj === 'function') {
|
||||||
|
return (obj as CloseWrapper<T>)(close);
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to show prompt with default stylings.
|
||||||
|
*/
|
||||||
|
export function usePrompt(): (info: PromptInfo) => void {
|
||||||
|
const showDialog = useDialog();
|
||||||
|
|
||||||
|
return (info: PromptInfo) => {
|
||||||
|
showDialog({
|
||||||
|
...info,
|
||||||
|
title: (
|
||||||
|
<Text variant='h2' as='h1' className='text-theme-black'>
|
||||||
|
{info.title}
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
description: (
|
||||||
|
<Text variant='p' as='p' className='text-ut-black'>
|
||||||
|
{info.description}
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
className: 'max-w-[400px] flex flex-col gap-2.5 p-6.25',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unique ID counter is safe to be global
|
||||||
|
let nextId = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows descendant to show dialogs via a function, handling animations and stacking.
|
||||||
|
*/
|
||||||
|
export default function DialogProvider(props: { children: ReactNode }): JSX.Element {
|
||||||
|
const dialogQueue = useRef<DialogElement[]>([]);
|
||||||
|
const [openDialog, setOpenDialog] = useState<DialogElement | undefined>();
|
||||||
|
const openRef = useRef<typeof openDialog>();
|
||||||
|
openRef.current = openDialog;
|
||||||
|
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
const showDialog = useCallback<ShowDialogFn>(info => {
|
||||||
|
const id = nextId++;
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const infoUnwrapped = unwrapCloseWrapper(info, handleClose);
|
||||||
|
const buttons = unwrapCloseWrapper(infoUnwrapped.buttons, handleClose);
|
||||||
|
|
||||||
|
const onLeave = () => {
|
||||||
|
setOpenDialog(undefined);
|
||||||
|
|
||||||
|
if (dialogQueue.current.length > 0) {
|
||||||
|
const newOpen = dialogQueue.current.pop();
|
||||||
|
setOpenDialog(() => newOpen);
|
||||||
|
setIsOpen(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
infoUnwrapped.onClose?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
const dialogElement = (show: boolean) => (
|
||||||
|
<Dialog
|
||||||
|
key={id}
|
||||||
|
onClose={handleClose}
|
||||||
|
afterLeave={onLeave}
|
||||||
|
title={infoUnwrapped.title}
|
||||||
|
description={infoUnwrapped.description}
|
||||||
|
appear
|
||||||
|
show={show}
|
||||||
|
className={infoUnwrapped.className}
|
||||||
|
>
|
||||||
|
<div className='mt-0.75 w-full flex justify-end gap-2.5'>{buttons}</div>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (openRef.current) {
|
||||||
|
dialogQueue.current.push(openRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
setOpenDialog(() => dialogElement);
|
||||||
|
setIsOpen(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogContext.Provider value={showDialog}>
|
||||||
|
{props.children}
|
||||||
|
|
||||||
|
{openDialog?.(isOpen)}
|
||||||
|
</DialogContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -13,4 +13,29 @@
|
|||||||
.extensionRoot {
|
.extensionRoot {
|
||||||
@apply font-sans h-full;
|
@apply font-sans h-full;
|
||||||
color: #303030;
|
color: #303030;
|
||||||
|
|
||||||
|
[data-rfd-drag-handle-context-id=':r1:'] {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
border: 3px solid #fff;
|
||||||
|
border-radius: 7px;
|
||||||
|
min-height: 40px;
|
||||||
|
box-shadow: none;
|
||||||
|
background: rgb(218, 220, 224);
|
||||||
|
}
|
||||||
|
:hover::-webkit-scrollbar-thumb,
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgb(189, 193, 198);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:active {
|
||||||
|
background: rgb(128, 134, 139);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,12 +42,12 @@ export default function PopupCourseBlock({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<div
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: colors.primaryColor,
|
backgroundColor: colors.primaryColor,
|
||||||
}}
|
}}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'h-full w-full inline-flex items-center justify-center gap-1 rounded pr-3 cursor-pointer focusable text-left',
|
'h-full w-full inline-flex items-center justify-center gap-1 rounded pr-3 focusable cursor-pointer text-left',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
@@ -56,7 +56,7 @@ export default function PopupCourseBlock({
|
|||||||
style={{
|
style={{
|
||||||
backgroundColor: colors.secondaryColor,
|
backgroundColor: colors.secondaryColor,
|
||||||
}}
|
}}
|
||||||
className='flex cursor-move items-center self-stretch rounded rounded-r-0'
|
className='flex items-center self-stretch rounded rounded-r-0 cursor-move!'
|
||||||
{...dragHandleProps}
|
{...dragHandleProps}
|
||||||
>
|
>
|
||||||
<DragIndicatorIcon className='h-6 w-6 text-white' />
|
<DragIndicatorIcon className='h-6 w-6 text-white' />
|
||||||
@@ -75,6 +75,6 @@ export default function PopupCourseBlock({
|
|||||||
<StatusIcon status={course.status} className='h-5 w-5' />
|
<StatusIcon status={course.status} className='h-5 w-5' />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</button>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Dialog, Transition } from '@headlessui/react';
|
import { Dialog, Transition, TransitionChild } from '@headlessui/react';
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ function PromptDialog({ isOpen, onClose, title, content, children }: PromptDialo
|
|||||||
return (
|
return (
|
||||||
<Transition appear show={isOpen} as={React.Fragment}>
|
<Transition appear show={isOpen} as={React.Fragment}>
|
||||||
<Dialog as='div' onClose={onClose} className='relative z-50'>
|
<Dialog as='div' onClose={onClose} className='relative z-50'>
|
||||||
<Transition.Child
|
<TransitionChild
|
||||||
as={React.Fragment}
|
as={React.Fragment}
|
||||||
enter='ease-out duration-200'
|
enter='ease-out duration-200'
|
||||||
enterFrom='opacity-0'
|
enterFrom='opacity-0'
|
||||||
@@ -38,9 +38,9 @@ function PromptDialog({ isOpen, onClose, title, content, children }: PromptDialo
|
|||||||
leaveTo='opacity-0'
|
leaveTo='opacity-0'
|
||||||
>
|
>
|
||||||
<div className='fixed inset-0 bg-black bg-opacity-50' aria-hidden='true' />
|
<div className='fixed inset-0 bg-black bg-opacity-50' aria-hidden='true' />
|
||||||
</Transition.Child>
|
</TransitionChild>
|
||||||
|
|
||||||
<Transition.Child
|
<TransitionChild
|
||||||
as={React.Fragment}
|
as={React.Fragment}
|
||||||
enter='ease-out duration-200'
|
enter='ease-out duration-200'
|
||||||
enterFrom='opacity-0 scale-95'
|
enterFrom='opacity-0 scale-95'
|
||||||
@@ -56,7 +56,7 @@ function PromptDialog({ isOpen, onClose, title, content, children }: PromptDialo
|
|||||||
<div className='flex items-center justify-end gap-2'>{children}</div>
|
<div className='flex items-center justify-end gap-2'>{children}</div>
|
||||||
</Dialog.Panel>
|
</Dialog.Panel>
|
||||||
</div>
|
</div>
|
||||||
</Transition.Child>
|
</TransitionChild>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Transition>
|
</Transition>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Disclosure, Transition } from '@headlessui/react';
|
import { Disclosure, DisclosureButton, DisclosurePanel, Transition } from '@headlessui/react';
|
||||||
import Text from '@views/components/common/Text/Text';
|
import Text from '@views/components/common/Text/Text';
|
||||||
import useSchedules from '@views/hooks/useSchedules';
|
import useSchedules from '@views/hooks/useSchedules';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@@ -25,7 +25,7 @@ export default function ScheduleDropdown(props: ScheduleDropdownProps) {
|
|||||||
<Disclosure defaultOpen={props.defaultOpen}>
|
<Disclosure defaultOpen={props.defaultOpen}>
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
<Disclosure.Button className='w-full flex items-center border-none bg-transparent px-3.5 py-2.5 text-left'>
|
<DisclosureButton className='w-full flex items-center border-none bg-transparent px-3.5 py-2.5 text-left'>
|
||||||
<div className='flex-1'>
|
<div className='flex-1'>
|
||||||
<Text as='div' variant='h4' className='mb-1 w-100% text-ut-burntorange'>
|
<Text as='div' variant='h4' className='mb-1 w-100% text-ut-burntorange'>
|
||||||
{(activeSchedule ? activeSchedule.name : 'Schedule').toUpperCase()}:
|
{(activeSchedule ? activeSchedule.name : 'Schedule').toUpperCase()}:
|
||||||
@@ -42,9 +42,10 @@ export default function ScheduleDropdown(props: ScheduleDropdownProps) {
|
|||||||
<Text className='text-ut-burntorange text-2xl! font-normal!'>
|
<Text className='text-ut-burntorange text-2xl! font-normal!'>
|
||||||
{open ? <DropdownArrowDown /> : <DropdownArrowUp />}
|
{open ? <DropdownArrowDown /> : <DropdownArrowUp />}
|
||||||
</Text>
|
</Text>
|
||||||
</Disclosure.Button>
|
</DisclosureButton>
|
||||||
|
|
||||||
<Transition
|
<Transition
|
||||||
|
as='div'
|
||||||
className='contain-paint max-h-55 origin-top overflow-auto transition-all duration-400 ease-in-out-expo'
|
className='contain-paint max-h-55 origin-top overflow-auto transition-all duration-400 ease-in-out-expo'
|
||||||
enterFrom='transform scale-98 opacity-0 max-h-0!'
|
enterFrom='transform scale-98 opacity-0 max-h-0!'
|
||||||
enterTo='transform scale-100 opacity-100 max-h-55'
|
enterTo='transform scale-100 opacity-100 max-h-55'
|
||||||
@@ -52,7 +53,7 @@ export default function ScheduleDropdown(props: ScheduleDropdownProps) {
|
|||||||
leaveFrom='transform scale-100 opacity-100 max-h-55'
|
leaveFrom='transform scale-100 opacity-100 max-h-55'
|
||||||
leaveTo='transform scale-98 opacity-0 max-h-0!'
|
leaveTo='transform scale-98 opacity-0 max-h-0!'
|
||||||
>
|
>
|
||||||
<Disclosure.Panel className='px-3.5 pb-2.5 pt-2'>{props.children}</Disclosure.Panel>
|
<DisclosurePanel className='px-3.5 pb-2.5 pt-2'>{props.children}</DisclosurePanel>
|
||||||
</Transition>
|
</Transition>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -56,7 +56,9 @@ export default function Description({ course }: DescriptionProps): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{status === LoadStatus.ERROR && (
|
{status === LoadStatus.ERROR && (
|
||||||
<Text color='theme-red'>Please refresh the page and log back in using your UT EID and password.</Text>
|
<Text className='text-theme-red font-bold!'>
|
||||||
|
Please refresh the page and log back in using your UT EID and password.
|
||||||
|
</Text>
|
||||||
)}
|
)}
|
||||||
{/* TODO (achadaga): would be nice to have a new spinner here */}
|
{/* TODO (achadaga): would be nice to have a new spinner here */}
|
||||||
{status === LoadStatus.LOADING && <Spinner />}
|
{status === LoadStatus.LOADING && <Spinner />}
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ export default function GradeDistribution({ course }: GradeDistributionProps): J
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='pb-[25px] pt-[12px]'>
|
<div className='pt-3'>
|
||||||
{/* TODO (achadaga): again would be nice to have an updated spinner */}
|
{/* TODO (achadaga): again would be nice to have an updated spinner */}
|
||||||
{status === DataStatus.LOADING && <Spinner />}
|
{status === DataStatus.LOADING && <Spinner />}
|
||||||
{status === DataStatus.NOT_FOUND && (
|
{status === DataStatus.NOT_FOUND && (
|
||||||
@@ -208,7 +208,7 @@ export default function GradeDistribution({ course }: GradeDistributionProps): J
|
|||||||
Grade Distribution for {course.department} {course.number}
|
Grade Distribution for {course.department} {course.number}
|
||||||
</Text>
|
</Text>
|
||||||
<select
|
<select
|
||||||
className='border border rounded-[4px] border-solid px-[12px] py-[8px]'
|
className='border border rounded border-solid px-3 py-2'
|
||||||
onChange={handleSelectSemester}
|
onChange={handleSelectSemester}
|
||||||
>
|
>
|
||||||
{Object.keys(distributions)
|
{Object.keys(distributions)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Chip, flagMap } from '@views/components/common/Chip';
|
|||||||
import Divider from '@views/components/common/Divider';
|
import Divider from '@views/components/common/Divider';
|
||||||
import Link from '@views/components/common/Link';
|
import Link from '@views/components/common/Link';
|
||||||
import Text from '@views/components/common/Text/Text';
|
import Text from '@views/components/common/Text/Text';
|
||||||
|
import { useCalendar } from '@views/contexts/CalendarContext';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import Add from '~icons/material-symbols/add';
|
import Add from '~icons/material-symbols/add';
|
||||||
@@ -15,6 +16,7 @@ import CloseIcon from '~icons/material-symbols/close';
|
|||||||
import Copy from '~icons/material-symbols/content-copy';
|
import Copy from '~icons/material-symbols/content-copy';
|
||||||
import Description from '~icons/material-symbols/description';
|
import Description from '~icons/material-symbols/description';
|
||||||
import Mood from '~icons/material-symbols/mood';
|
import Mood from '~icons/material-symbols/mood';
|
||||||
|
import OpenNewIcon from '~icons/material-symbols/open-in-new';
|
||||||
import Remove from '~icons/material-symbols/remove';
|
import Remove from '~icons/material-symbols/remove';
|
||||||
import Reviews from '~icons/material-symbols/reviews';
|
import Reviews from '~icons/material-symbols/reviews';
|
||||||
|
|
||||||
@@ -50,6 +52,7 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
|
|||||||
const { courseName, department, number: courseNumber, uniqueId, instructors, flags, schedule } = course;
|
const { courseName, department, number: courseNumber, uniqueId, instructors, flags, schedule } = course;
|
||||||
const courseAdded = activeSchedule.courses.some(ourCourse => ourCourse.uniqueId === uniqueId);
|
const courseAdded = activeSchedule.courses.some(ourCourse => ourCourse.uniqueId === uniqueId);
|
||||||
const formattedUniqueId = uniqueId.toString().padStart(5, '0');
|
const formattedUniqueId = uniqueId.toString().padStart(5, '0');
|
||||||
|
const isInCalendar = useCalendar();
|
||||||
|
|
||||||
const getInstructorFullName = (instructor: Instructor) => {
|
const getInstructorFullName = (instructor: Instructor) => {
|
||||||
const { firstName = '', lastName = '' } = instructor;
|
const { firstName = '', lastName = '' } = instructor;
|
||||||
@@ -85,9 +88,13 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleOpenPastSyllabi = async () => {
|
const handleOpenPastSyllabi = async () => {
|
||||||
// not specific to professor
|
for (const instructor of instructors) {
|
||||||
const url = `https://utdirect.utexas.edu/apps/student/coursedocs/nlogon/?year=&semester=&department=${department}&course_number=${courseNumber}&course_title=${courseName}&unique=&instructor_first=&instructor_last=&course_type=In+Residence&search=Search`;
|
let { firstName = '', lastName = '' } = instructor;
|
||||||
|
firstName = capitalizeString(firstName);
|
||||||
|
lastName = capitalizeString(lastName);
|
||||||
|
const url = `https://utdirect.utexas.edu/apps/student/coursedocs/nlogon/?year=&semester=&department=${department}&course_number=${courseNumber}&course_title=&unique=&instructor_first=${firstName}&instructor_last=${lastName}&course_type=In+Residence&search=Search`;
|
||||||
openNewTab({ url });
|
openNewTab({ url });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddOrRemoveCourse = async () => {
|
const handleAddOrRemoveCourse = async () => {
|
||||||
@@ -100,7 +107,7 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full px-2 pb-3 pt-6 text-ut-black'>
|
<div className='w-full px-2 pb-3 pt-5 text-ut-black'>
|
||||||
<div className='flex flex-col'>
|
<div className='flex flex-col'>
|
||||||
<div className='flex items-center gap-1'>
|
<div className='flex items-center gap-1'>
|
||||||
<Text variant='h1' className='truncate text-theme-black'>
|
<Text variant='h1' className='truncate text-theme-black'>
|
||||||
@@ -175,8 +182,16 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
|
|||||||
<Button
|
<Button
|
||||||
variant='filled'
|
variant='filled'
|
||||||
color='ut-burntorange'
|
color='ut-burntorange'
|
||||||
icon={CalendarMonth}
|
icon={isInCalendar ? OpenNewIcon : CalendarMonth}
|
||||||
onClick={() => background.switchToCalendarTab({})}
|
onClick={() => {
|
||||||
|
if (isInCalendar) {
|
||||||
|
openNewTab({
|
||||||
|
url: course.url,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
background.switchToCalendarTab({});
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Divider size='1.75rem' orientation='vertical' />
|
<Divider size='1.75rem' orientation='vertical' />
|
||||||
<Button variant='outline' color='ut-blue' icon={Reviews} onClick={handleOpenRateMyProf}>
|
<Button variant='outline' color='ut-blue' icon={Reviews} onClick={handleOpenRateMyProf}>
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { createPortal } from 'react-dom';
|
|||||||
|
|
||||||
import styles from './RecruitmentBanner.module.scss';
|
import styles from './RecruitmentBanner.module.scss';
|
||||||
|
|
||||||
const DISCORD_URL = 'https://discord.gg/qjcvgyVJbT';
|
const DISCORD_URL = 'https://discord.gg/7pQDBGdmb7';
|
||||||
const GITHUB_URL = 'https://github.com/sghsri/UT-Registration-Plus';
|
const GITHUB_URL = 'https://github.com/Longhorn-Developers/UT-Registration-Plus';
|
||||||
|
|
||||||
const RECRUIT_FROM_DEPARTMENTS = ['C S', 'ECE', 'MIS', 'CSE', 'EE', 'ITD'];
|
const RECRUIT_FROM_DEPARTMENTS = ['C S', 'ECE', 'MIS', 'CSE', 'EE', 'ITD'];
|
||||||
|
|
||||||
|
|||||||
11
src/views/contexts/CalendarContext.ts
Normal file
11
src/views/contexts/CalendarContext.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { createContext, useContext } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context for the calendar.
|
||||||
|
*/
|
||||||
|
export const CalendarContext = createContext(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns The calendar context.
|
||||||
|
*/
|
||||||
|
export const useCalendar = () => useContext(CalendarContext);
|
||||||
32
src/views/contexts/DialogContext.ts
Normal file
32
src/views/contexts/DialogContext.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { createContext, useContext } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close wrapper
|
||||||
|
*/
|
||||||
|
export type CloseWrapper<T> = (close: () => void) => T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information about a dialog.
|
||||||
|
*/
|
||||||
|
export interface DialogInfo {
|
||||||
|
title?: JSX.Element;
|
||||||
|
description?: JSX.Element;
|
||||||
|
className?: string;
|
||||||
|
buttons?: JSX.Element | CloseWrapper<JSX.Element>;
|
||||||
|
onClose?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to show a dialog.
|
||||||
|
*/
|
||||||
|
export type ShowDialogFn = (info: DialogInfo | CloseWrapper<DialogInfo>) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context for the dialog provider.
|
||||||
|
*/
|
||||||
|
export const DialogContext = createContext<ShowDialogFn>(() => {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns The dialog context for showing dialogs.
|
||||||
|
*/
|
||||||
|
export const useDialog = () => useContext(DialogContext);
|
||||||
@@ -122,11 +122,27 @@ export class CourseCatalogScraper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets how many credit hours the course is worth
|
* Gets how many credit hours the course is worth
|
||||||
* @param number the course number, CS 314H
|
* @param courseNumber the course number, CS 314H
|
||||||
* @return the number of credit hours the course is worth
|
* @return the number of credit hours the course is worth
|
||||||
*/
|
*/
|
||||||
getCreditHours(number: string): number {
|
getCreditHours(courseNumber: string): number {
|
||||||
return Number(number.split('')[0]);
|
let creditHours = Number(courseNumber.split('')[0]);
|
||||||
|
const lastChar = courseNumber.slice(-1);
|
||||||
|
|
||||||
|
// eslint-disable-next-line default-case
|
||||||
|
switch (lastChar) {
|
||||||
|
case 'A':
|
||||||
|
case 'B':
|
||||||
|
creditHours /= 2;
|
||||||
|
break;
|
||||||
|
case 'X':
|
||||||
|
case 'Y':
|
||||||
|
case 'Z':
|
||||||
|
creditHours /= 3;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return creditHours;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export default defineConfig({
|
|||||||
rules: [
|
rules: [
|
||||||
[
|
[
|
||||||
'btn-transition',
|
'btn-transition',
|
||||||
{ transition: 'color 180ms, border-color 150ms, background-color 150ms, box-shadow 0ms, transform 50ms' },
|
{ transition: 'color 180ms, border-color 150ms, background-color 150ms, box-shadow 50ms, transform 50ms' },
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'ring-offset-0',
|
'ring-offset-0',
|
||||||
|
|||||||
Reference in New Issue
Block a user