Compare commits

...

73 Commits
mus ... main

Author SHA1 Message Date
5164255aea
feat(ide): Add Project Default inspection profile
This commit includes a new inspection profile named 'Project Default'. This profile is configured to detect duplicated code in TypeScript with a weak warning level. It is enabled by default.
2024-06-20 15:09:41 +02:00
53be13ef38
feat(components): Add buy-modal component for purchasing crypto
This commit introduces a new component, `buy-modal.tsx`, within the cryptos scope of components. This component allows the option to buy cryptocurrency either from a user or directly from the server. It provides form validation, API integrations, handles states, and displays proper response messages.
2024-06-20 15:09:14 +02:00
49d485c11a
feat(components): Add buy-modal component for purchasing crypto
This commit introduces a new component, `buy-modal.tsx`, within the cryptos scope of components. This component allows the option to buy cryptocurrency either from a user or directly from the server. It provides form validation, API integrations, handles states, and displays proper response messages.
2024-06-20 15:07:15 +02:00
37b7116a69
feat(cryptos): Add ViewModal component
A new ViewModal component was added to the cryptos directory. This React component includes Dialog properties and widgets from ui components such as a Button, Label, and Input. Modifications also include a LineChart from lucide-react library.
2024-06-20 15:05:57 +02:00
2452d8d607
feat: Add new file
This commit introduces a new file to the codebase. No pre-existing files or functionalities have been modified or removed as part of this change.
2024-06-20 15:03:27 +02:00
b188e8573d
feat(component): add DataTable component
This commit introduces a new DataTable component in the UI component library. It includes features like column sorting, pagination, and customizable filtering. This reusable component is designed to handle and display data tables across different parts of the application.
2024-06-20 15:02:44 +02:00
fb4b2bb7c1
feat(components): add CryptosTable and WalletTable data-tables components
Introduce CryptosTable and WalletTable components. CryptosTable is responsible for displaying cryptocurrency information available for purchase, while WalletTable displays the user's current cryptocurrency holdings. Both components utilize react-table for table generation and sorting functionality. Additional "Buy" and "View" modals have been configured for CryptosTable.
2024-06-20 15:00:22 +02:00
f268294b93
feat: Add new dashboard, admin, and wallet pages
Implement new page.tsx files for dashboard, admin, and wallet modules, providing essential UI logic and data-fetching mechanisms. These components handle their respective data retrieval and loading states, and present the information via relevant UI structures such as 'CryptosTable' and 'WalletTable'.
2024-06-20 14:59:28 +02:00
90dd1d0828
feat: add @tanstack/react-table dependency to package
This commit includes the addition of @tanstack/react-table package as a project dependency. Changes were made in both package.json and pnpm-lock.yaml files to reflect this addition. This new package contributes enhanced table functionalities to the project.
2024-06-20 14:58:30 +02:00
2c9fafd802
feat(layout): add Wallet and Dashboard buttons
Imported Link and Button components into layout.tsx, which were used to add Wallet and Dashboard navigational buttons to the header section. The buttons have been set with a 'light' variant and flex features for responsive design.
2024-06-20 14:57:03 +02:00
4e0de3be06
feat(apiRequest): use JSON.parse for accessToken in Authorization header
The Authorization header in the apiRequest now utilizes JSON.parse for the accessToken. This change ensures proper parsing and retrieval of the item from the local storage when the window object is defined.
2024-06-20 14:56:35 +02:00
a2f2996ef9
feat(apiRequest): use JSON.parse for accessToken in Authorization header
The Authorization header in the apiRequest now utilizes JSON.parse for the accessToken. This change ensures proper parsing and retrieval of the item from the local storage when the window object is defined.
2024-06-20 14:55:23 +02:00
0a239f4478
feat(interfaces): Add new interfaces
New interfaces related to referral code and trade request are added to the `api.interface.ts`. Additionally, import statement modified to include `ICryptoInWalletInfo` into the module. This enriches the module with a set of necessary interfaces providing better control over referral codes, trades, and offers operations.
2024-06-20 14:52:07 +02:00
69484cc90f
feat(component): add wallet loading state and user wallet information
The account-info component has been updated to include a loading state for user wallets, a feature that significantly improves the performance of the component by preventing unnecessary re-renders. The IUserWallet type now includes new fields to store wallet data such as cryptocurrencies' details and owned amount. These changes also include display adjustments and improved routing for 'My Wallet' button.
2024-06-20 14:45:12 +02:00
3819a0f338
feat(handler): Update imports and remove excess functions
Updated the import statement within account.handler.ts to feature new, relevant components. Also, this commit includes a significant clean-up of the account.handler service, where a large amount of superfluous function calls were removed to streamline the code.
2024-06-20 14:44:31 +02:00
977b13d46a
feat(button component): Add 'light' style variation to button
This commit introduces a new 'light' style variation for the button component. Now a button can be styled with 'light' variation which changes the button's background and text color on hover state.
2024-06-19 22:26:17 +02:00
aa322d0c8f
test(temp): temporary file from Quentin 2024-06-18 22:49:36 +02:00
87245c702e
feat(service): implement trade and referral code functions in account handler
The commit introduces implementation for functions handling trading and referral codes in account.handler.ts. These includes `createTrade`, `getAllTrade`, `getUserTrade`, `getAllReferralCode`, `createReferralCode` functions. The update improves the overall functionality of trade and promotional related processes. A new interface `IAllReferralCodeRes` is also imported for referral code response.
2024-06-18 22:49:07 +02:00
e7f6de4a29
feat(ide): add discord.xml file
This commit includes the creation of a new discord.xml file under the .idea directory. This config file brings in new settings for the DiscordProject related to its 'show' and 'description' options.
2024-06-18 21:33:43 +02:00
b9d47ba401
style(footer): adjust div spacing in footer component
This commit properly adjusts the spacing in an empty div within the footer section of the React component for adherence to standardized code formatting. Proper spacing contributes to code readability and maintainability.
2024-06-18 21:33:17 +02:00
d54d05403b
feat(auth-form): enhance user registration and login process
This commit introduces some improvements to the user registration and login processes. It standardizes the update interval in the user data upon registration, fixes the missing 'toString' call for 'access_token' and improves code readability by correcting indents and adding extra spaces. Additionally, this commit refactors the redirection logic after successful login or registration, making it more robust and reliable.
2024-06-18 21:32:49 +02:00
1898d554f9
feat(interfaces): add new API interfaces and update existing ones
New interfaces were added to enhance functionality for trade and offer creation requests. Renamed IApiAllTrades interface to IApiAllTradesRes for consistency. Interfaces were also added to manage referral codes and rankings.
2024-06-18 21:22:49 +02:00
50225f1c17
feat(account-info): import and use getWallet from account handler
The 'getWallet' function has been imported from the account handler service and is now used in account-info component. This addition ensures to fetch information when the component renders.
2024-06-18 21:22:34 +02:00
00be94c5a8
feat(account.handler): add new API methods
This commit adds several new async functions to fetch wallet, user trades and all trades in `account.handler.ts`. This provides an interface for interacting with multiple new endpoints. Additionally, several import statements have been updated to reflect the changes.
2024-06-18 21:22:12 +02:00
faba9fa3cb
refactor(layout): rearrange import order
The import statements in layout.tsx have been reordered. React type import is now following Toaster component import to maintain organized import sequence.
2024-06-18 16:36:56 +02:00
397bef5cdf
refactor: move general interface to interfaces directory
This commit relocates the 'general.interface.ts' file from the 'services' directory to the 'interfaces' directory. This structural change is meant to streamline the organization of the code base.
2024-06-18 16:35:46 +02:00
11b4c723fa
chore(dependencies): upgrade packages in pnpm-lock.yaml
Several dependencies have been upgraded across multiple versions including react-hook-form, lightweight-charts, next elements, sonner, lucide-react, embla-carousel-react and others. Reflecting these changes in the pnpm-lock.yaml file is necessary for functional and up-to-date building of the project. This helps maintain compatibility and introduces performance improvements or new features from the updated packages.
2024-06-17 10:02:36 +02:00
1674664980
build: Update package dependencies
Upgrade multiple dependencies to their newer versions in package.json file. This includes libraries like 'embla-carousel-react', 'lightweight-charts', and 'react' among others. It also includes devDependencies like '@types/node', '@types/react', and 'typescript'.
2024-06-17 10:02:09 +02:00
39fc556aca
refactor(app): move Toaster component position in layout
The Toaster component was relocated in the layout file. It's been moved from below the Providers component to a position above the Footer component. This repositioning is aimed at improving the visual hierarchy and flow of the application.
2024-06-17 10:01:32 +02:00
7426f5f642
feat(component): Update footer styling and structure
The footer's class has been updated to improve page layout. Additionally, unnecessary div containing flex properties has been removed for simplicity and improved readability.
2024-06-17 10:01:15 +02:00
01c073e879
feat(account-info): add disconnect handling and improve info display
The commit enhances the account information component with better data handling. It adds disconnect handling to account for cases when user session is terminated. It also improves information display by adding details like crypto availability and user identity, improving the overall user experience.
2024-06-17 09:50:06 +02:00
e6d37ef600
feat(services): add general.interface.ts
This commit introduces a new interface file named 'general.interface.ts' to standardise return types in the services scope. It includes the IStandardisedReturn interface and the EReturnState enum. These additions enhance the consistency of return types across different services.
2024-06-17 09:49:09 +02:00
cc286462f0
feat(components): add authentication forms
This commit introduces authentication forms for both user login and registration. These forms use zod for data validation and context for state management. Login and registration processes have been implemented as asynchronous functions, handling API requests, and error responses.
2024-06-17 09:48:38 +02:00
4c061dc19c
feat(account-dialog): handle case when userContext not present
This commit updates the account-dialog component to properly handle when there's no userContext. Previously, a default user data was set when no userContext was found, this has been replaced with a simple message saying 'No account'. Also, checks for an authentication token in localStorage have been included. These changes aim towards better handling of edge cases and unauthenticated scenarios.
2024-06-17 09:47:57 +02:00
3b1a3e93e0
feat(layout): add Toaster component and update favicon link
This commit introduces the Toaster component to the main layout. In addition, the link to the favicon has been updated to correct its location.
2024-06-17 09:46:21 +02:00
5c81ad917d
feat(auth): Update auth page layout
The width of the authentication page is adjusted to occupy the full width of the viewport to enhance usability. This layout update ensures consistency for different screen sizes.
2024-06-17 09:45:58 +02:00
b5526e5877
"refactor(service): remove register and login functions in account.handler.ts"
This commit involves a significant refactoring in the account.handler.ts file. Specifically, it removed the `doRegister` and `doLogin` functions. Furthermore, replaced `useEncodedLocalStorage` with `useLocalStorage`. The `doDisconnect` function has been refined to redirect to homepage after removing an item from local storage.
2024-06-17 09:45:25 +02:00
41ba50d417
feat(ui): Change input background color to accent
In the 'input.tsx' component of the UI, the background color property of the input field has been updated from 'bg-background' to 'bg-accent' to enhance visibility.
2024-06-17 09:44:47 +02:00
0504692dfb
feat(auto-form): apply full width to form class
The className of the form in the auto-form component has been updated to include "w-full". This change ensures that the form will occupy the full width of its parent container, enhancing layout and visibility.
2024-06-17 09:44:07 +02:00
15eb7addd0
feat(interface): allow message to be string or array
This update to the api.interface now accepts both strings and arrays for the 'message' field inside the IAbstractApiResponse interface. This provides more flexibility for responses returned by the API.
2024-06-17 09:43:26 +02:00
5a905d0608
refactor: moved favicon to app root 2024-06-17 09:35:31 +02:00
72bbe08de0
feat(components): update layout for responsiveness
This commit enhances the layout in header.tsx for better mobile responsiveness. It adjusts flex properties and classes for optimal rendering across various device sizes. It also rearranges elements within the header component for a better mobile appearance.
2024-06-14 12:48:03 +02:00
48adc8be6c
refactor(interface): modify import statement in userdata.interface.ts
This change adjusts the import statement to correctly import the ICryptoInUserWalletInfo. Previously, the incorrect syntax was causing import errors and this revision resolves that issue.
2024-06-14 12:47:41 +02:00
3a8621735e
feat(auth): Add new authentication page
A new authentication page has been added to the application. It includes an AuthForms component and uses a flex display for layout, with specific height and width properties set.
2024-06-14 12:47:17 +02:00
0ead6bd969
refactor(interfaces): add Wallet related properties in User and Crypto interfaces
The change adds Wallet related properties in both User and Crypto interfaces. Specifically, `IUserWallet` interface has been added to `userdata.interface.ts` and 'amount' field has been added to `IUserWalletCryptos` in `crypto.interface.ts`. Also, an extra type `ICryptoInUserWalletInfo` extending `ICryptoInWalletInfo` has been added with `owned_amount` field.
2024-06-14 10:08:44 +02:00
747cc1cdb4
feat(ui-component): add new copy button component
This commit introduces a new CopyButton component in the ui components. It also includes a feature to copy multiple choices. The button changes its icon after copying to clipboard, indicating that the copying has been done.
2024-06-14 10:07:58 +02:00
a873095099
feat(component): Add loading and context handling
The update enhances the account dialog component by introducing loading state management and improved context handling. Instead of displaying "Loading..." while user data is being fetched, a Skeleton component is now rendered. Additionally, detailed user data fields are now provided when setting the user data context. An effect hook supplies the proper loading state management.
2024-06-12 16:54:18 +02:00
600692e3e5
feat(component): Add loading and context handling
The update enhances the account dialog component by introducing loading state management and improved context handling. Instead of displaying "Loading..." while user data is being fetched, a Skeleton component is now rendered. Additionally, detailed user data fields are now provided when setting the user data context. An effect hook supplies the proper loading state management.
2024-06-12 15:13:23 +02:00
efb6eaf274
feat(component): modify CSS classes for improved layout
Updated the classes in the Header component to enhance layout presentation. Particularly, added padding at the bottom and updated the alignment for better responsiveness. This will enhance user interface on multiple device dimensions.
2024-06-12 15:00:33 +02:00
b3907697a0
feat(ui): add DatePicker component
A new DatePicker component has been added to the UI components directory. This component includes a dropdown calendar for a user-friendly date selector. It utilizes a popover triggered by a button for seamless date selection and integration.
2024-06-12 15:00:05 +02:00
c51e4fcc77
feat(auto-form): add new fields and form files to AutoForm component
Added several files to the AutoForm component for better form functionality including a file for each type of input field (checkbox, date, enum, file, number, radio-group, switch, etc). Managed the configurations in a separate file and handled dependencies to control form behaviours. This commit enhances form handling capabilities and simplifies the process for creating versatile forms.
2024-06-12 14:59:35 +02:00
6976e3a317
feat(component): replace Sheet with Dialog, enhance user interaction
The 'Sheet' UI component in 'account-info' component is replaced with a 'Dialog'. This commit also upgrades userData delivery, and includes interactivity enhancements like added wallet and disconnect buttons. The user's first name is presented in the dialog header.
2024-06-12 14:58:25 +02:00
24c168bc44
feat(interface): add new interfaces for user assets and trades
This commit includes two additional interfaces for handling user assets and trades. The `IApiUserAssetsRes` interface provides a way to store and manage the user assets response from the API. The `IApiAllTrades` interface has been introduced for dealing with all trades data. Necessary imports are also added in the file.
2024-06-12 14:50:50 +02:00
f3fd897a3f
feat(interfaces): Add crypto interface
This commit adds the crypto interface file to the src/interfaces directory. The file includes the IUserWalletCryptos, ICryptoInWalletInfo, IAllTrades, ITrade, ISellerIdentity, IBuyerIdentity, and ITradedCrypto interfaces, defining the structure and data types for the user wallet cryptos, all trades, and traded crypto.
2024-06-12 14:50:28 +02:00
3dfee64e8e
feat: Add new biome.json configuration file
This commit introduces a new biome.json file which contains configuration for organizing imports, included files, version control, linter rules and formatting parameters. It's intended to optimize code quality checks and make the codebase more maintainable.
2024-06-12 09:49:28 +02:00
dea98f41a9
feat: add '@biomejs/biome' as a devDependency
This commit introduces '@biomejs/biome' version 1.8.1 as a devDependency in the pnpm-lock.yaml file. This also includes additions of '@biomejs' related CLI tools for various platforms (darwin, linux, win32) with different architectures (x64, arm64) as optional dependencies, providing support for multiple environments.
2024-06-12 09:48:56 +02:00
cfa18733b4
feat(package): add biome check script and biome devDependency
This commit adds a new "check" script in the package.json that uses Biome for checking our codebase. It also includes Biome as a new development dependency to facilitate the use of the check script.
2024-06-12 09:48:28 +02:00
a7899d8a4a
feat(services): refactor localStorage service
The updated code refactors the `localStorage.ts` service. The refactoring introduces typescript syntax on the import statement, adds a safer use of localStorage when window is undefined, and enhances readability of the code by modifying the function signature to multiple lines. Additionally, it removes unnecessary lines for cleaner code structure.
2024-06-12 09:47:56 +02:00
1c42b6a4b6
feat: Improve code formatting and import order
Rearranged import orders for better visibility and readability. Also, cleaned up some of the typescript and JSX by adding appropriate line breaks and spaces, and ensuring the use of semicolons for better punctuation.
2024-06-12 09:47:17 +02:00
4d00d4b936
feat(handler): add login, registration functionality
The login and registration functionality have been implemented using the new `IApiLoginReq`, `IApiLoginRes`, `IApiRegisterReq`, `IApiRegisterRes` interfaces. These include error handling and data storing in local storage.
2024-06-11 16:59:02 +02:00
2c333d9c00
feat(component): update user data references from name to firstName
The changes replace the "name" property references with "firstName" in the account-info component. The user interface buttons' labels and input field placeholders now utilize "firstName" for user data. This adjustment provides clearer and more accurate data presentation to the users.
2024-06-11 16:58:43 +02:00
697dcbf4b8
feat(services): update baseUrl in apiRequest
This commit updates the baseUrl value in the apiRequest service. It now uses the API_URL from the environment variables, or defaults to "http://localhost:3333" if the environment variable is not set.
2024-06-11 16:55:45 +02:00
278cf844c2
feat(services): update apiRequest module
Updated the apiRequest module with refinements in authorization and removal of redundant comments. The changes allow handling server-side rendering and type enforcement in request body.
2024-06-11 16:08:57 +02:00
e9048ca7eb
feat(interfaces): add API request/response interfaces
Created new typescript interfaces for API. They include IApiRegisterReq, IApiLoginReq, IAbstractApiResponse, IApiRegisterRes, and IApiLoginRes. These interfaces will be used to properly structure the data for API requests and responses, thus enhancing type safety and maintainability.
2024-06-11 16:08:34 +02:00
950cb9137f
feat(interface): extend IUserData properties
This commit enriches the IUserData interface by adding a series of new properties for a more comprehensive representation of a user. These additional properties include id, firstName, lastName, pseudo, email, roleId, isActive, city, dollarAvailables, age, created_at, and updated_at.
2024-06-11 16:07:59 +02:00
882729ffc9
Revert "feat: Implement new auto-form components and fields"
This reverts commit dc17e4a8f755ce045b461858ffcd56f567c5658f.
2024-06-10 11:58:40 +02:00
dfa443d373
feat: update and remove specific dependencies
Details:
This update makes three major changes in dependency tree. Firstly, packages '@react-three/drei', '@react-three/fiber', 'three', 'three-globe', '@types/three', '@types/jest' and 'jest' have been removed. Secondly, the '@babel/core' package has been added as a new dependency. Thirdly, version of the 'next' package has been updated. Please review compatibility before upgrading.
2024-06-10 11:54:25 +02:00
dc17e4a8f7
feat: Implement new auto-form components and fields
This commit includes the creation of new react components and fields for auto-form functionality. The components include AutoFormLabel, AutoFormTooltip, and specific field components for data types such as AutoFormObject, AutoFormArray, AutoFormDate, AutoFormCheckbox, and others. Added tests to ensure the correct rendering of fields, labels generation, among other behaviors.
2024-06-10 11:51:23 +02:00
036acfce23
feat(services): update default value in UserDataContext
The default value in the UserDataContext has been updated. Previously, it was set to false and now it has been changed to an object with the name property set to "Avnyr".
2024-06-10 11:50:05 +02:00
606f37e78f
feat: Add new packages to pnpm-lock
This commit consists of adding new packages to the `pnpm-lock.yaml` file. Packages include '@radix-ui/react-navigation-menu', '@react-three/drei', 'lightweight-charts', 'three', 'three-globe', '@types/three', among others. Also, it includes the addition of new package versions and their integrity hashes. Verify the diff for full changes.
2024-06-10 11:35:54 +02:00
1dd0384857
feat(components, services): Add user data management and account handling
This commit introduces user data management with the addition of new interfaces and services. User data interface which defines the structure of user data was created. Account handler service for registering, logging in, updating user data and disconnecting was also added. User data provider component was created for managing user data state. The account info component for displaying and editing the user account was introduced too. Finally, updates were made in the existing components and services for accommodating these new elements.
2024-06-07 16:57:37 +02:00
d624ac6ab2
feat(page): update layout styling
The structure of the main component in page.tsx has been adjusted to better accommodate its content. The div styling has been updated from full width with padding to half-width, full height, and content justified to the end.
2024-06-07 14:34:31 +02:00
409926a97b
feat(tailwind.config): add global CSS variables for colors
A new function, `addVariablesForColors`, has been added into the Tailwind configuration file to map each Tailwind color into a global CSS variable. This allows usage such as `var(--gray-200)` throughout the application for all Tailwind colors.
2024-06-07 14:26:48 +02:00
110 changed files with 8863 additions and 3507 deletions

7
.idea/discord.xml generated Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="APPLICATION" />
<option name="description" value="" />
</component>
</project>

View File

@ -0,0 +1,10 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages>
<language minSize="86" name="TypeScript" />
</Languages>
</inspection_tool>
</profile>
</component>

37
biome.json Normal file
View File

@ -0,0 +1,37 @@
{
"$schema": "https://biomejs.dev/schemas/1.6.4/schema.json",
"organizeImports": {
"enabled": true
},
"files": {
"include": [
"./src/**/*.ts",
"./src/**/*.tsx"
]
},
"vcs": {
"enabled": true,
"clientKind": "git"
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"performance": {
"recommended": true,
"noDelete": "off"
},
"suspicious": {
"noExplicitAny": "warn"
},
"complexity": {
"useLiteralKeys": "off"
}
}
},
"formatter": {
"indentStyle": "tab",
"indentWidth": 2,
"lineWidth": 90
}
}

View File

@ -6,7 +6,8 @@
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint" "lint": "next lint",
"check": "biome check --skip-errors --apply src"
}, },
"dependencies": { "dependencies": {
"@fontsource-variable/kode-mono": "^5.0.3", "@fontsource-variable/kode-mono": "^5.0.3",
@ -23,6 +24,7 @@
"@radix-ui/react-hover-card": "^1.0.7", "@radix-ui/react-hover-card": "^1.0.7",
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-menubar": "^1.0.4", "@radix-ui/react-menubar": "^1.0.4",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-radio-group": "^1.1.3",
@ -37,34 +39,39 @@
"@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-toggle": "^1.0.3",
"@radix-ui/react-toggle-group": "^1.0.4", "@radix-ui/react-toggle-group": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7", "@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/react-table": "^8.17.3",
"axios": "^1.7.2", "axios": "^1.7.2",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.0.0", "cmdk": "^1.0.0",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"embla-carousel-react": "^8.1.3", "embla-carousel-react": "^8.1.5",
"framer-motion": "^11.2.10", "framer-motion": "^11.2.10",
"input-otp": "^1.2.4", "input-otp": "^1.2.4",
"lucide-react": "^0.387.0", "lightweight-charts": "^4.1.5",
"next": "14.2.3", "lucide-react": "^0.395.0",
"next": "14.2.4",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"react": "^18", "react": "^18.3.1",
"react-day-picker": "^8.10.1", "react-day-picker": "^8.10.1",
"react-dom": "^18", "react-dom": "^18.3.1",
"react-hook-form": "^7.51.5", "react-hook-form": "^7.52.0",
"react-resizable-panels": "^2.0.19", "react-resizable-panels": "^2.0.19",
"sonner": "^1.4.41", "sonner": "^1.5.0",
"tailwind-merge": "^2.3.0", "tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"vaul": "^0.9.1", "vaul": "^0.9.1",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20", "@biomejs/biome": "1.8.1",
"@types/react": "^18", "@types/jest": "^29.5.12",
"@types/react-dom": "^18", "@types/node": "^20.14.2",
"postcss": "^8", "@types/react": "^18.3.3",
"tailwindcss": "^3.4.1", "@types/react-dom": "^18.3.0",
"typescript": "^5" "jest": "^29.7.0",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.4",
"typescript": "^5.4.5"
} }
} }

2606
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

10
src/app/auth/page.tsx Normal file
View File

@ -0,0 +1,10 @@
import { AuthForms } from "@/components/auth-form";
import Image from "next/image";
export default function AuthPage() {
return (
<main className="flex flex-col items-center justify-start h-full w-full">
<AuthForms />
</main>
);
}

View File

@ -0,0 +1,7 @@
export default function AdminPage() {
return (<>
<section>
<h1>Welcome to the Dashboard</h1>
</section>
</>)
}

View File

@ -0,0 +1,44 @@
"use client"
import type {ICryptoInWalletInfo} from "@/interfaces/crypto.interface";
import {CryptosTable} from "@/components/data-tables/cryptos-table";
import {useEffect, useState} from "react";
import {Skeleton} from "@/components/ui/skeleton";
import ApiRequest from "@/services/apiRequest";
import type {IApiAllOffersRes} from "@/interfaces/api.interface";
export default function DashboardPage() {
const [isLoading, setIsLoading] = useState<boolean>(true)
const [cryptosList, setCryptosList] = useState<ICryptoInWalletInfo[]>([])
//FIX the loop
useEffect(() => {
ApiRequest.authenticated.get.json<ICryptoInWalletInfo[]>(
"crypto/all"
).then((response)=>{
if (response.data.length <= 0) {
setCryptosList([])
setIsLoading(false)
return
}
const resp = response.data
setCryptosList(resp)
setIsLoading(false)
})
}, []);
if (isLoading) {
return (<div className={"container flex flex-col justify-center items-center min-h-64 my-6 gap-4"}>
<h1 className={"text-2xl font-bold"}>Available cryptos</h1>
<Skeleton className={"container flex flex-col justify-center items-center min-h-64 my-6 gap-4"}/>
</div>)
}
return (<>
<section className={"container flex flex-col justify-center items-center min-h-64 my-6 gap-4"}>
<h1 className={"text-2xl font-bold"}>Available cryptos</h1>
<CryptosTable cryptosArray={cryptosList}/>
</section>
</>)
}

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,15 +1,21 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import '@fontsource-variable/kode-mono'; import "@fontsource-variable/kode-mono";
import "./globals.css"; import "./globals.css";
import {ThemeProvider} from "@/components/providers/theme-provider"; import { Footer } from "@/components/footer";
import { Header } from "@/components/header";
import { PrimaryNavigationMenu } from "@/components/primary-nav";
import { Providers } from "@/components/providers/providers";
import { ThemeProvider } from "@/components/providers/theme-provider";
import { Toaster } from "@/components/ui/toaster";
import type React from "react"; import type React from "react";
import {Footer} from "@/components/footer"; import Link from "next/link";
import {Header} from "@/components/header"; import {Button} from "@/components/ui/button";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "YeloBit", title: "YeloBit",
description: "Generated by create next app", description: "Generated by create next app",
icons: "yellow-bit.svg"}; icons: "yellow-bit.svg",
};
export default function RootLayout({ export default function RootLayout({
children, children,
@ -19,18 +25,20 @@ export default function RootLayout({
return ( return (
<html lang="en"> <html lang="en">
<head> <head>
<link rel="icon" href="/public/favicon.ico" sizes="any"/> <link rel="icon" href="/favicon.ico" sizes="any" />
</head> </head>
<body className={"w-full min-h-screen flex flex-col items-center justify-between"}> <body className={"w-full min-h-screen flex flex-col items-center justify-between"}>
<ThemeProvider <Providers>
attribute="class" <Header>
defaultTheme="system" <div className={"flex flex-row flex-wrap md:flex-nowrap gap-2"}>
enableSystem <Button asChild variant={'light'}><Link href={'/wallet'}>Wallet</Link></Button>
> <Button asChild variant={'light'}><Link href={'/dashboard'}>Dashboard</Link></Button>
<Header></Header> </div>
</Header>
{children} {children}
<Footer/> <Toaster />
</ThemeProvider> <Footer />
</Providers>
</body> </body>
</html> </html>
); );

View File

@ -2,7 +2,7 @@ import Image from "next/image";
export default function Home() { export default function Home() {
return ( return (
<main className="flex flex-col items-center justify-between p-24"> <main className="flex flex-col items-center justify-end h-full w-2/4">
<h1>Hello world !</h1> <h1>Hello world !</h1>
</main> </main>
); );

40
src/app/wallet/page.tsx Normal file
View File

@ -0,0 +1,40 @@
"use client"
import type {ICryptoInWalletInfo} from "@/interfaces/crypto.interface";
import {CryptosTable} from "@/components/data-tables/cryptos-table";
import {useContext, useEffect, useState} from "react";
import {Skeleton} from "@/components/ui/skeleton";
import ApiRequest from "@/services/apiRequest";
import type {IApiAllOffersRes} from "@/interfaces/api.interface";
import {WalletTable} from "@/components/data-tables/wallet-table";
import {UserDataContext} from "@/components/providers/userdata-provider";
import {IUserWallet} from "@/interfaces/userdata.interface";
export default function WalletPage() {
const [isLoading, setIsLoading] = useState<boolean>(true)
const [cryptosList, setCryptosList] = useState<ICryptoInWalletInfo[]>([])
const userContext = useContext(UserDataContext);
//FIX the loop
useEffect(() => {
console.log(userContext?.userData)
if (userContext?.userData) {
setIsLoading(false)
}
}, [userContext]);
if (isLoading || !userContext?.userData) {
return (<div className={"container flex flex-col justify-center items-center min-h-64 my-6 gap-4"}>
<h1 className={"text-2xl font-bold"}>Cryptos in your wallet</h1>
<Skeleton className={"container flex flex-col justify-center items-center min-h-64 my-6 gap-4"}/>
</div>)
}
return (<>
<section className={"container flex flex-col justify-center items-center min-h-64 my-6 gap-4"}>
<h1 className={"text-2xl font-bold"}>Cryptos in your wallet</h1>
<WalletTable walletArray={userContext.userData.wallet as unknown as IUserWallet}/>
</section>
</>)
}

View File

@ -0,0 +1,53 @@
"use client";
import { AccountInfo } from "@/components/account-info";
import { UserDataContext } from "@/components/providers/userdata-provider";
import { Skeleton } from "@/components/ui/skeleton";
import type { IUserData } from "@/interfaces/userdata.interface";
import {
type Dispatch,
type SetStateAction,
useContext,
useEffect,
useState,
} from "react";
const localStorage = typeof window !== "undefined" ? window.localStorage : null;
export function AccountDialog() {
const userContext = useContext(UserDataContext);
const token = localStorage?.getItem("sub") || "";
const haveToken = token.length >= 16 || false;
console.log(haveToken);
const [isLoaded, setIsLoaded] = useState<boolean>(false);
if (!userContext) {
return (
<div>
<p>No account</p>
</div>
);
}
useEffect(() => {
if (userContext?.userData) {
setIsLoaded(true);
}
}, [userContext?.userData]);
if (!isLoaded) {
return <Skeleton className="w-14 h-10 rounded" />;
}
return (
<div>
<AccountInfo
userData={userContext?.userData as IUserData}
setUserData={
userContext?.setUserData as Dispatch<SetStateAction<IUserData | undefined>>
}
isDisconnected={!haveToken}
/>
</div>
);
}

View File

@ -0,0 +1,171 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import type {IUserData, IUserWallet} from "@/interfaces/userdata.interface";
import { CopyButton } from "@/components/ui/copy-button";
import { doDisconnect, getWallet } from "@/services/account.handler";
import { Bitcoin, Fingerprint, Key, Landmark, Unplug, User, Wallet } from "lucide-react";
import Link from "next/link";
import type React from "react";
import {useEffect, useState} from "react";
import {type ICryptoInUserWalletInfo, ICryptoInWalletInfo} from "@/interfaces/crypto.interface";
export function AccountInfo({
userData,
setUserData,
isDisconnected,
}: {
userData: IUserData;
setUserData: React.Dispatch<React.SetStateAction<IUserData | undefined>>;
isDisconnected: boolean;
}) {
const [isLoaded,setIsLoaded] = useState(false)
useEffect(() => {
if (!isLoaded) {
getWallet().then((res) => {
const wallet: IUserWallet = {
uat: Date.now(),
update_interval: 30_000,
owned_cryptos: res.resolved?.UserHasCrypto?.map((el): ICryptoInUserWalletInfo => {
return {
id: el.Crypto.id,
name: el.Crypto.name,
value: el.Crypto.value,
image: el.Crypto.image,
quantity: el.Crypto.quantity,
owned_amount: el.amount,
created_at: el.Crypto.created_at,
updated_at: el.Crypto.updated_at
}
}) || []
}
delete res.resolved?.UserHasCrypto
//@ts-ignore
setUserData({
...userData,
...res.resolved,
wallet: wallet
})
console.log(userData)
setIsLoaded(true)
});
}
}, [isLoaded]);
if (isDisconnected) {
return (
<div className={"flex flex-col justify-center items-center h-10 p-2 text-xs mt-2"}>
<div
className={
"flex flex-row justify-center items-center gap-1 text-destructive to-red-900 animate-pulse"
}
>
<Unplug className={"w-4"} />
<p>Disconnected</p>
</div>
<div>
<Link
href={"/auth"}
className={
"hover:text-primary flex justify-evenly items-center gap-1 p-1 text-nowrap"
}
>
<Key className={"w-3"} /> Link account
</Link>
</div>
</div>
);
}
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline" className={"gap-2 px-2"}>
<p>{userData.firstName}</p>
<User />
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[720px]">
<DialogHeader>
<DialogTitle>{`Your account - ${userData.firstName} ${userData.lastName}`}</DialogTitle>
<DialogDescription>{userData.city}</DialogDescription>
</DialogHeader>
<div className={"flex flex-col items-center justify-center w-full"}>
<div className={"flex flex-col justify-evenly items-center gap-2"}>
<div
className={
"flex flex-col md:flex-row gap-2 justify-center md:justify-evenly items-start md:items-center w-full"
}
>
<div
className={
"flex gap-1 justify-start md:justify-center items-center mx-auto w-full md:w-fit"
}
>
<Landmark />
<p className={"rounded bg-accent text-accent-foreground p-1"}>
{userData.dollarAvailables} $
</p>
</div>
<div
className={
"flex gap-1 justify-start md:justify-center items-center mx-auto w-full md:w-fit"
}
>
<Bitcoin />
<p className={"rounded bg-accent text-accent-foreground p-1"}>
{`You currently have ${userData.wallet.owned_cryptos.length} crypto(s)`}
</p>
</div>
</div>
<div
className={"flex flex-col gap-3 justify-center items-start mx-auto mt-4"}
>
<div className={"flex flex-row text-nowrap flex-nowrap gap-1 text-primary"}>
<Fingerprint />
<h2>Your identity</h2>
</div>
<div
className={
"font-light text-xs md:text-sm flex flex-row items-center justify-start gap-1 bg-accent p-2 rounded"
}
>
<p>{userData.id}</p>
<CopyButton value={userData.id} />
</div>
</div>
</div>
</div>
<DialogFooter>
<Button variant={"secondary"} className={"gap-2 px-2"} asChild>
<Link href={'/wallet'}>
<Wallet />
<p>My wallet</p>
</Link>
</Button>
<Button
variant={"destructive"}
className={"gap-2 px-2 mb-2"}
onClick={() => doDisconnect()}
>
<Unplug />
<p>Disconnect</p>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@ -0,0 +1,247 @@
"use client";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import AutoForm, { AutoFormSubmit } from "@/components/auto-form";
import { UserDataContext } from "@/components/providers/userdata-provider";
import { ToastBox, toastType } from "@/components/ui/toast-box";
import { useToast } from "@/components/ui/use-toast";
import type {
IApiLoginReq,
IApiLoginRes,
IApiRegisterReq,
IApiRegisterRes,
} from "@/interfaces/api.interface";
import { EReturnState, type IStandardisedReturn } from "@/interfaces/general.interface";
import type { IUserData } from "@/interfaces/userdata.interface";
import ApiRequest from "@/services/apiRequest";
import { useLocalStorage } from "@/services/localStorage";
import { Bug, RefreshCw } from "lucide-react";
import Link from "next/link";
import { type Dispatch, type SetStateAction, useContext, useState } from "react";
import * as z from "zod";
const loginSchema = z.object({
email: z
.string({
required_error: "Email is needed.",
})
.email({
message: "Should be a valid email.",
})
.describe("Your account email."),
password: z
.string({
required_error: "Password is needed.",
})
.describe("Your account password."),
});
const registerSchema = z.object({
firstName: z.string({
required_error: "",
}),
lastName: z.string(),
age: z.number().min(18).max(120),
pseudo: z.string({
required_error: "",
}),
city: z.string({
required_error: "",
}),
email: z
.string({
required_error: "Email is needed.",
})
.email("Should be a valid email."),
password: z
.string({
required_error: "Password is needed.",
})
.describe("Your account password."),
});
export function AuthForms() {
const [isLoading, setIsLoading] = useState(false);
const [sub, setSub] = useLocalStorage<string | undefined>("sub", "");
const userContext = useContext(UserDataContext);
const { toast } = useToast();
async function doRegister(
registerData: IApiRegisterReq,
userDataSetter: Dispatch<SetStateAction<IUserData | null>>,
): Promise<IStandardisedReturn<IApiRegisterRes>> {
console.trace(registerData);
try {
const ReqRes = await ApiRequest.standard.post.json<
IApiRegisterReq,
IApiRegisterRes
>("auth/signup", registerData);
console.trace(ReqRes.data);
if (ReqRes.data.user) {
userDataSetter({
...ReqRes.data.user,
wallet: {
uat: Date.now(),
update_interval: 30_000,
owned_cryptos: [],
},
});
setSub(ReqRes.data.access_token);
}
console.debug(ReqRes.data.message || "Not additional message from request");
return {
state: EReturnState.done,
resolved: ReqRes.data,
};
} catch (error) {
console.error("Error during registration:", error);
return {
state: EReturnState.serverError,
message: error as string,
};
}
}
async function doLogin(
loginData: IApiLoginReq,
): Promise<IStandardisedReturn<IApiLoginRes>> {
try {
const ReqRes = await ApiRequest.standard.post.json<IApiLoginReq, IApiLoginRes>(
"auth/signin",
loginData,
);
console.trace(ReqRes.data);
if (ReqRes.data.access_token) {
setSub(ReqRes.data.access_token);
}
return {
state: EReturnState.done,
};
} catch (err) {
console.error("Error during login:", err);
return {
state: EReturnState.serverError,
message: err as string,
};
}
}
if (!userContext || !userContext.setUserData) {
return (
<div
className={
"bg-destructive text-destructive-foreground p-3 gap-2 border rounded flex flex-row justify-center items-center"
}
>
<Bug />
<p>It seems that the context is missing..</p>
</div>
);
}
return (
<Tabs defaultValue="login" className="w-full p-2 md:w-[400px] my-4">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="login">Login</TabsTrigger>
<TabsTrigger value="register">Register</TabsTrigger>
</TabsList>
<TabsContent value="login">
<AutoForm
// Pass the schema to the form
formSchema={loginSchema}
onSubmit={(data: IApiLoginReq) => {
setIsLoading(true);
doLogin(data).then((res) => {
if (res.state !== EReturnState.done) {
toast({
description: res.message || "An unexpected error occurred..",
variant: "destructive",
});
setIsLoading(false);
return;
}
//toast.custom(<ToastBox message={"Login successful ! \n You will be redirected."} type={toastType.success}/>)
toast({
description: "Login successful ! \n You will be redirected.",
});
setTimeout(() => {
setIsLoading(false);
location.href = "/";
console.log("Moving to home.");
}, 3_000);
});
}}
fieldConfig={{
password: {
inputProps: {
type: "password",
placeholder: "••••••••",
},
},
}}
>
<AutoFormSubmit
disabled={!!isLoading}
className={"gap-2 disabled:bg-secondary"}
>
{/* biome-ignore lint/style/useTemplate: <explanation> */}
<RefreshCw className={"animate-spin" + isLoading && "hidden"} />
<p>Login</p>
</AutoFormSubmit>
</AutoForm>
</TabsContent>
<TabsContent value="register">
<AutoForm
// Pass the schema to the form
formSchema={registerSchema}
onSubmit={(data: IApiRegisterReq) => {
setIsLoading(true);
doRegister(
data,
userContext.setUserData as Dispatch<SetStateAction<IUserData | null>>,
).then((res) => {
if (res.state !== EReturnState.done) {
//toast.custom(<ToastBox message={res.message || "An unexpected error occurred.."} type={toastType.error}/>)
setIsLoading(false);
return;
}
//toast.custom(<ToastBox message={"Register successful ! \n You will be redirected."} type={toastType.success}/>)
setTimeout(() => {
setIsLoading(false);
//location.href = "/"
console.log("Moving to home.");
}, 5_000);
});
}}
fieldConfig={{
password: {
inputProps: {
type: "password",
placeholder: "••••••••",
},
},
}}
>
<AutoFormSubmit
disabled={!!isLoading}
className={"gap-2 disabled:bg-secondary"}
>
{/* biome-ignore lint/style/useTemplate: <explanation> */}
<RefreshCw className={"animate-spin" + !isLoading && "hidden"} />
<p>Register</p>
</AutoFormSubmit>
<p className="text-gray-500 text-sm">
By submitting this form, you agree to our{" "}
<Link href="#" className="text-primary underline">
terms and conditions
</Link>
.
</p>
</AutoForm>
</TabsContent>
</Tabs>
);
}

View File

@ -0,0 +1,23 @@
import { FormLabel } from "@/components/ui/form";
import { cn } from "@/lib/utils";
function AutoFormLabel({
label,
isRequired,
className,
}: {
label: string;
isRequired: boolean;
className?: string;
}) {
return (
<>
<FormLabel className={cn(className)}>
{label}
{isRequired && <span className="text-destructive"> *</span>}
</FormLabel>
</>
);
}
export default AutoFormLabel;

View File

@ -0,0 +1,13 @@
function AutoFormTooltip({ fieldConfigItem }: { fieldConfigItem: any }) {
return (
<>
{fieldConfigItem?.description && (
<p className="text-sm text-gray-500 dark:text-white">
{fieldConfigItem.description}
</p>
)}
</>
);
}
export default AutoFormTooltip;

View File

@ -0,0 +1,35 @@
import AutoFormCheckbox from "./fields/checkbox";
import AutoFormDate from "./fields/date";
import AutoFormEnum from "./fields/enum";
import AutoFormFile from "./fields/file";
import AutoFormInput from "./fields/input";
import AutoFormNumber from "./fields/number";
import AutoFormRadioGroup from "./fields/radio-group";
import AutoFormSwitch from "./fields/switch";
import AutoFormTextarea from "./fields/textarea";
export const INPUT_COMPONENTS = {
checkbox: AutoFormCheckbox,
date: AutoFormDate,
select: AutoFormEnum,
radio: AutoFormRadioGroup,
switch: AutoFormSwitch,
textarea: AutoFormTextarea,
number: AutoFormNumber,
file: AutoFormFile,
fallback: AutoFormInput,
};
/**
* Define handlers for specific Zod types.
* You can expand this object to support more types.
*/
export const DEFAULT_ZOD_HANDLERS: {
[key: string]: keyof typeof INPUT_COMPONENTS;
} = {
ZodBoolean: "checkbox",
ZodDate: "date",
ZodEnum: "select",
ZodNativeEnum: "select",
ZodNumber: "number",
};

View File

@ -0,0 +1,57 @@
import type { FieldValues, UseFormWatch } from "react-hook-form";
import type * as z from "zod";
import { type Dependency, DependencyType, type EnumValues } from "./types";
export default function resolveDependencies<
SchemaType extends z.infer<z.ZodObject<any, any>>,
>(
dependencies: Dependency<SchemaType>[],
currentFieldName: keyof SchemaType,
watch: UseFormWatch<FieldValues>,
) {
let isDisabled = false;
let isHidden = false;
let isRequired = false;
let overrideOptions: EnumValues | undefined;
const currentFieldValue = watch(currentFieldName as string);
const currentFieldDependencies = dependencies.filter(
(dependency) => dependency.targetField === currentFieldName,
);
for (const dependency of currentFieldDependencies) {
const watchedValue = watch(dependency.sourceField as string);
const conditionMet = dependency.when(watchedValue, currentFieldValue);
switch (dependency.type) {
case DependencyType.DISABLES:
if (conditionMet) {
isDisabled = true;
}
break;
case DependencyType.REQUIRES:
if (conditionMet) {
isRequired = true;
}
break;
case DependencyType.HIDES:
if (conditionMet) {
isHidden = true;
}
break;
case DependencyType.SETS_OPTIONS:
if (conditionMet) {
overrideOptions = dependency.options;
}
break;
}
}
return {
isDisabled,
isHidden,
isRequired,
overrideOptions,
};
}

View File

@ -0,0 +1,91 @@
import {
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { Plus, Trash } from "lucide-react";
import { useFieldArray, type useForm } from "react-hook-form";
import * as z from "zod";
import { beautifyObjectName } from "../utils";
import AutoFormObject from "./object";
function isZodArray(item: z.ZodArray<any> | z.ZodDefault<any>): item is z.ZodArray<any> {
return item instanceof z.ZodArray;
}
function isZodDefault(
item: z.ZodArray<any> | z.ZodDefault<any>,
): item is z.ZodDefault<any> {
return item instanceof z.ZodDefault;
}
export default function AutoFormArray({
name,
item,
form,
path = [],
fieldConfig,
}: {
name: string;
item: z.ZodArray<any> | z.ZodDefault<any>;
form: ReturnType<typeof useForm>;
path?: string[];
fieldConfig?: any;
}) {
const { fields, append, remove } = useFieldArray({
control: form.control,
name,
});
const title = item._def.description ?? beautifyObjectName(name);
const itemDefType = isZodArray(item)
? item._def.type
: isZodDefault(item)
? item._def.innerType._def.type
: null;
return (
<AccordionItem value={name} className="border-none">
<AccordionTrigger>{title}</AccordionTrigger>
<AccordionContent>
{fields.map((_field, index) => {
const key = _field.id;
return (
<div className="mt-4 flex flex-col" key={`${key}`}>
<AutoFormObject
schema={itemDefType as z.ZodObject<any, any>}
form={form}
fieldConfig={fieldConfig}
path={[...path, index.toString()]}
/>
<div className="my-4 flex justify-end">
<Button
variant="secondary"
size="icon"
type="button"
className="hover:bg-zinc-300 hover:text-black focus:ring-0 focus:ring-offset-0 focus-visible:ring-0 focus-visible:ring-offset-0 dark:bg-white dark:text-black dark:hover:bg-zinc-300 dark:hover:text-black dark:hover:ring-0 dark:hover:ring-offset-0 dark:focus-visible:ring-0 dark:focus-visible:ring-offset-0"
onClick={() => remove(index)}
>
<Trash className="size-4 " />
</Button>
</div>
<Separator />
</div>
);
})}
<Button
type="button"
variant="secondary"
onClick={() => append({})}
className="mt-4 flex items-center"
>
<Plus className="mr-2" size={16} />
Add
</Button>
</AccordionContent>
</AccordionItem>
);
}

View File

@ -0,0 +1,34 @@
import { Checkbox } from "@/components/ui/checkbox";
import { FormControl, FormItem } from "@/components/ui/form";
import AutoFormLabel from "../common/label";
import AutoFormTooltip from "../common/tooltip";
import type { AutoFormInputComponentProps } from "../types";
export default function AutoFormCheckbox({
label,
isRequired,
field,
fieldConfigItem,
fieldProps,
}: AutoFormInputComponentProps) {
return (
<div>
<FormItem>
<div className="mb-3 flex items-center gap-3">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
{...fieldProps}
/>
</FormControl>
<AutoFormLabel
label={fieldConfigItem?.label || label}
isRequired={isRequired}
/>
</div>
</FormItem>
<AutoFormTooltip fieldConfigItem={fieldConfigItem} />
</div>
);
}

View File

@ -0,0 +1,25 @@
import { DatePicker } from "@/components/ui/date-picker";
import { FormControl, FormItem, FormMessage } from "@/components/ui/form";
import AutoFormLabel from "../common/label";
import AutoFormTooltip from "../common/tooltip";
import type { AutoFormInputComponentProps } from "../types";
export default function AutoFormDate({
label,
isRequired,
field,
fieldConfigItem,
fieldProps,
}: AutoFormInputComponentProps) {
return (
<FormItem>
<AutoFormLabel label={fieldConfigItem?.label || label} isRequired={isRequired} />
<FormControl>
<DatePicker date={field.value} setDate={field.onChange} {...fieldProps} />
</FormControl>
<AutoFormTooltip fieldConfigItem={fieldConfigItem} />
<FormMessage />
</FormItem>
);
}

View File

@ -0,0 +1,59 @@
import { FormControl, FormItem, FormMessage } from "@/components/ui/form";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import type * as z from "zod";
import AutoFormLabel from "../common/label";
import AutoFormTooltip from "../common/tooltip";
import type { AutoFormInputComponentProps } from "../types";
import { getBaseSchema } from "../utils";
export default function AutoFormEnum({
label,
isRequired,
field,
fieldConfigItem,
zodItem,
fieldProps,
}: AutoFormInputComponentProps) {
const baseValues = (getBaseSchema(zodItem) as unknown as z.ZodEnum<any>)._def.values;
let values: [string, string][] = [];
if (!Array.isArray(baseValues)) {
values = Object.entries(baseValues);
} else {
values = baseValues.map((value) => [value, value]);
}
function findItem(value: any) {
return values.find((item) => item[0] === value);
}
return (
<FormItem>
<AutoFormLabel label={fieldConfigItem?.label || label} isRequired={isRequired} />
<FormControl>
<Select onValueChange={field.onChange} defaultValue={field.value} {...fieldProps}>
<SelectTrigger className={fieldProps.className}>
<SelectValue placeholder={fieldConfigItem.inputProps?.placeholder}>
{field.value ? findItem(field.value)?.[1] : "Select an option"}
</SelectValue>
</SelectTrigger>
<SelectContent>
{values.map(([value, label]) => (
<SelectItem value={label} key={value}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
<AutoFormTooltip fieldConfigItem={fieldConfigItem} />
<FormMessage />
</FormItem>
);
}

View File

@ -0,0 +1,64 @@
import { FormControl, FormItem, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Trash2 } from "lucide-react";
import { type ChangeEvent, useState } from "react";
import AutoFormLabel from "../common/label";
import AutoFormTooltip from "../common/tooltip";
import type { AutoFormInputComponentProps } from "../types";
export default function AutoFormFile({
label,
isRequired,
fieldConfigItem,
fieldProps,
field,
}: AutoFormInputComponentProps) {
const { showLabel: _showLabel, ...fieldPropsWithoutShowLabel } = fieldProps;
const showLabel = _showLabel === undefined ? true : _showLabel;
const [file, setFile] = useState<string | null>(null);
const [fileName, setFileName] = useState<string | null>(null);
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
setFile(reader.result as string);
setFileName(file.name);
field.onChange(reader.result as string);
};
reader.readAsDataURL(file);
}
};
const handleRemoveClick = () => {
setFile(null);
};
return (
<FormItem>
{showLabel && (
<AutoFormLabel label={fieldConfigItem?.label || label} isRequired={isRequired} />
)}
{!file && (
<FormControl>
<Input
type="file"
{...fieldPropsWithoutShowLabel}
onChange={handleFileChange}
value={""}
/>
</FormControl>
)}
{file && (
<div className="flex h-[40px] w-full flex-row items-center justify-between space-x-2 rounded-sm border p-2 text-black focus-visible:ring-0 focus-visible:ring-offset-0 dark:bg-white dark:text-black dark:focus-visible:ring-0 dark:focus-visible:ring-offset-0">
<p>{fileName}</p>
<button onClick={handleRemoveClick} aria-label="Remove image">
<Trash2 size={16} />
</button>
</div>
)}
<AutoFormTooltip fieldConfigItem={fieldConfigItem} />
<FormMessage />
</FormItem>
);
}

View File

@ -0,0 +1,34 @@
import { FormControl, FormItem, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import AutoFormLabel from "../common/label";
import AutoFormTooltip from "../common/tooltip";
import type { AutoFormInputComponentProps } from "../types";
export default function AutoFormInput({
label,
isRequired,
fieldConfigItem,
fieldProps,
}: AutoFormInputComponentProps) {
const { showLabel: _showLabel, ...fieldPropsWithoutShowLabel } = fieldProps;
const showLabel = _showLabel === undefined ? true : _showLabel;
const type = fieldProps.type || "text";
return (
<div className="flex flex-row items-center space-x-2">
<FormItem className="flex w-full flex-col justify-start">
{showLabel && (
<AutoFormLabel
label={fieldConfigItem?.label || label}
isRequired={isRequired}
/>
)}
<FormControl>
<Input type={type} {...fieldPropsWithoutShowLabel} />
</FormControl>
<AutoFormTooltip fieldConfigItem={fieldConfigItem} />
<FormMessage />
</FormItem>
</div>
);
}

View File

@ -0,0 +1,28 @@
import { FormControl, FormItem, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import AutoFormLabel from "../common/label";
import AutoFormTooltip from "../common/tooltip";
import type { AutoFormInputComponentProps } from "../types";
export default function AutoFormNumber({
label,
isRequired,
fieldConfigItem,
fieldProps,
}: AutoFormInputComponentProps) {
const { showLabel: _showLabel, ...fieldPropsWithoutShowLabel } = fieldProps;
const showLabel = _showLabel === undefined ? true : _showLabel;
return (
<FormItem>
{showLabel && (
<AutoFormLabel label={fieldConfigItem?.label || label} isRequired={isRequired} />
)}
<FormControl>
<Input type="number" {...fieldPropsWithoutShowLabel} />
</FormControl>
<AutoFormTooltip fieldConfigItem={fieldConfigItem} />
<FormMessage />
</FormItem>
);
}

View File

@ -0,0 +1,175 @@
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { FormField } from "@/components/ui/form";
import { type useForm, useFormContext } from "react-hook-form";
import * as z from "zod";
import { DEFAULT_ZOD_HANDLERS, INPUT_COMPONENTS } from "../config";
import resolveDependencies from "../dependencies";
import type { Dependency, FieldConfig, FieldConfigItem } from "../types";
import {
beautifyObjectName,
getBaseSchema,
getBaseType,
zodToHtmlInputProps,
} from "../utils";
import AutoFormArray from "./array";
function DefaultParent({ children }: { children: React.ReactNode }) {
return <>{children}</>;
}
export default function AutoFormObject<SchemaType extends z.ZodObject<any, any>>({
schema,
form,
fieldConfig,
path = [],
dependencies = [],
}: {
schema: SchemaType | z.ZodEffects<SchemaType>;
form: ReturnType<typeof useForm>;
fieldConfig?: FieldConfig<z.infer<SchemaType>>;
path?: string[];
dependencies?: Dependency<z.infer<SchemaType>>[];
}) {
const { watch } = useFormContext(); // Use useFormContext to access the watch function
if (!schema) {
return null;
}
const { shape } = getBaseSchema<SchemaType>(schema) || {};
if (!shape) {
return null;
}
const handleIfZodNumber = (item: z.ZodAny) => {
const isZodNumber = (item as any)._def.typeName === "ZodNumber";
const isInnerZodNumber = (item._def as any).innerType?._def?.typeName === "ZodNumber";
if (isZodNumber) {
(item as any)._def.coerce = true;
} else if (isInnerZodNumber) {
(item._def as any).innerType._def.coerce = true;
}
return item;
};
return (
<Accordion type="multiple" className="space-y-5 border-none">
{Object.keys(shape).map((name) => {
let item = shape[name] as z.ZodAny;
item = handleIfZodNumber(item) as z.ZodAny;
const zodBaseType = getBaseType(item);
const itemName = item._def.description ?? beautifyObjectName(name);
const key = [...path, name].join(".");
const {
isHidden,
isDisabled,
isRequired: isRequiredByDependency,
overrideOptions,
} = resolveDependencies(dependencies, name, watch);
if (isHidden) {
return null;
}
if (zodBaseType === "ZodObject") {
return (
<AccordionItem value={name} key={key} className="border-none">
<AccordionTrigger>{itemName}</AccordionTrigger>
<AccordionContent className="p-2">
<AutoFormObject
schema={item as unknown as z.ZodObject<any, any>}
form={form}
fieldConfig={
(fieldConfig?.[name] ?? {}) as FieldConfig<z.infer<typeof item>>
}
path={[...path, name]}
/>
</AccordionContent>
</AccordionItem>
);
}
if (zodBaseType === "ZodArray") {
return (
<AutoFormArray
key={key}
name={name}
item={item as unknown as z.ZodArray<any>}
form={form}
fieldConfig={fieldConfig?.[name] ?? {}}
path={[...path, name]}
/>
);
}
const fieldConfigItem: FieldConfigItem = fieldConfig?.[name] ?? {};
const zodInputProps = zodToHtmlInputProps(item);
const isRequired =
isRequiredByDependency ||
zodInputProps.required ||
fieldConfigItem.inputProps?.required ||
false;
if (overrideOptions) {
item = z.enum(overrideOptions) as unknown as z.ZodAny;
}
return (
<FormField
control={form.control}
name={key}
key={key}
render={({ field }) => {
const inputType =
fieldConfigItem.fieldType ??
DEFAULT_ZOD_HANDLERS[zodBaseType] ??
"fallback";
const InputComponent =
typeof inputType === "function" ? inputType : INPUT_COMPONENTS[inputType];
const ParentElement = fieldConfigItem.renderParent ?? DefaultParent;
const defaultValue = fieldConfigItem.inputProps?.defaultValue;
const value = field.value ?? defaultValue ?? "";
const fieldProps = {
...zodToHtmlInputProps(item),
...field,
...fieldConfigItem.inputProps,
disabled: fieldConfigItem.inputProps?.disabled || isDisabled,
ref: undefined,
value: value,
};
if (InputComponent === undefined) {
return <></>;
}
return (
<ParentElement key={`${key}.parent`}>
<InputComponent
zodInputProps={zodInputProps}
field={field}
fieldConfigItem={fieldConfigItem}
label={itemName}
isRequired={isRequired}
zodItem={item}
fieldProps={fieldProps}
className={fieldProps.className}
/>
</ParentElement>
);
}}
/>
);
})}
</Accordion>
);
}

View File

@ -0,0 +1,51 @@
import { FormControl, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import type * as z from "zod";
import AutoFormLabel from "../common/label";
import AutoFormTooltip from "../common/tooltip";
import type { AutoFormInputComponentProps } from "../types";
import { getBaseSchema } from "../utils";
export default function AutoFormRadioGroup({
label,
isRequired,
field,
zodItem,
fieldProps,
fieldConfigItem,
}: AutoFormInputComponentProps) {
const baseValues = (getBaseSchema(zodItem) as unknown as z.ZodEnum<any>)._def.values;
let values: string[] = [];
if (!Array.isArray(baseValues)) {
values = Object.entries(baseValues).map((item) => item[0]);
} else {
values = baseValues;
}
return (
<div>
<FormItem>
<AutoFormLabel label={fieldConfigItem?.label || label} isRequired={isRequired} />
<FormControl>
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
{...fieldProps}
>
{values?.map((value: any) => (
<FormItem key={value} className="mb-2 flex items-center gap-3 space-y-0">
<FormControl>
<RadioGroupItem value={value} />
</FormControl>
<FormLabel className="font-normal">{value}</FormLabel>
</FormItem>
))}
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
<AutoFormTooltip fieldConfigItem={fieldConfigItem} />
</div>
);
}

View File

@ -0,0 +1,34 @@
import { FormControl, FormItem } from "@/components/ui/form";
import { Switch } from "@/components/ui/switch";
import AutoFormLabel from "../common/label";
import AutoFormTooltip from "../common/tooltip";
import type { AutoFormInputComponentProps } from "../types";
export default function AutoFormSwitch({
label,
isRequired,
field,
fieldConfigItem,
fieldProps,
}: AutoFormInputComponentProps) {
return (
<div>
<FormItem>
<div className="flex items-center gap-3">
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
{...fieldProps}
/>
</FormControl>
<AutoFormLabel
label={fieldConfigItem?.label || label}
isRequired={isRequired}
/>
</div>
</FormItem>
<AutoFormTooltip fieldConfigItem={fieldConfigItem} />
</div>
);
}

View File

@ -0,0 +1,27 @@
import { FormControl, FormItem, FormMessage } from "@/components/ui/form";
import { Textarea } from "@/components/ui/textarea";
import AutoFormLabel from "../common/label";
import AutoFormTooltip from "../common/tooltip";
import type { AutoFormInputComponentProps } from "../types";
export default function AutoFormTextarea({
label,
isRequired,
fieldConfigItem,
fieldProps,
}: AutoFormInputComponentProps) {
const { showLabel: _showLabel, ...fieldPropsWithoutShowLabel } = fieldProps;
const showLabel = _showLabel === undefined ? true : _showLabel;
return (
<FormItem>
{showLabel && (
<AutoFormLabel label={fieldConfigItem?.label || label} isRequired={isRequired} />
)}
<FormControl>
<Textarea {...fieldPropsWithoutShowLabel} />
</FormControl>
<AutoFormTooltip fieldConfigItem={fieldConfigItem} />
<FormMessage />
</FormItem>
);
}

View File

@ -0,0 +1,117 @@
"use client";
import { Form } from "@/components/ui/form";
import type React from "react";
import { useEffect } from "react";
import { type DefaultValues, type FormState, useForm } from "react-hook-form";
import type { z } from "zod";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { zodResolver } from "@hookform/resolvers/zod";
import AutoFormObject from "@/components/auto-form/fields/object";
import type { Dependency, FieldConfig } from "@/components/auto-form/types";
import {
type ZodObjectOrWrapped,
getDefaultValues,
getObjectFormSchema,
} from "@/components/auto-form/utils";
export function AutoFormSubmit({
children,
className,
disabled,
}: {
children?: React.ReactNode;
className?: string;
disabled?: boolean;
}) {
return (
<Button type="submit" disabled={disabled} className={className}>
{children ?? "Submit"}
</Button>
);
}
function AutoForm<SchemaType extends ZodObjectOrWrapped>({
formSchema,
values: valuesProp,
onValuesChange: onValuesChangeProp,
onParsedValuesChange,
onSubmit: onSubmitProp,
fieldConfig,
children,
className,
dependencies,
}: {
formSchema: SchemaType;
values?: Partial<z.infer<SchemaType>>;
onValuesChange?: (values: Partial<z.infer<SchemaType>>) => void;
onParsedValuesChange?: (values: Partial<z.infer<SchemaType>>) => void;
onSubmit?: (values: z.infer<SchemaType>) => void;
fieldConfig?: FieldConfig<z.infer<SchemaType>>;
children?:
| React.ReactNode
| ((formState: FormState<z.infer<SchemaType>>) => React.ReactNode);
className?: string;
dependencies?: Dependency<z.infer<SchemaType>>[];
}) {
const objectFormSchema = getObjectFormSchema(formSchema);
const defaultValues: DefaultValues<z.infer<typeof objectFormSchema>> | null =
getDefaultValues(objectFormSchema, fieldConfig);
const form = useForm<z.infer<typeof objectFormSchema>>({
resolver: zodResolver(formSchema),
defaultValues: defaultValues ?? undefined,
values: valuesProp,
});
function onSubmit(values: z.infer<typeof formSchema>) {
const parsedValues = formSchema.safeParse(values);
if (parsedValues.success) {
onSubmitProp?.(parsedValues.data);
}
}
const values = form.watch();
// valuesString is needed because form.watch() returns a new object every time
const valuesString = JSON.stringify(values);
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
onValuesChangeProp?.(values);
const parsedValues = formSchema.safeParse(values);
if (parsedValues.success) {
onParsedValuesChange?.(parsedValues.data);
}
}, [valuesString]);
const renderChildren =
typeof children === "function"
? children(form.formState as FormState<z.infer<SchemaType>>)
: children;
return (
<div className="w-full">
<Form {...form}>
<form
onSubmit={(e) => {
form.handleSubmit(onSubmit)(e);
}}
className={cn("space-y-5 w-full", className)}
>
<AutoFormObject
schema={objectFormSchema}
form={form}
dependencies={dependencies}
fieldConfig={fieldConfig}
/>
{renderChildren}
</form>
</Form>
</div>
);
}
export default AutoForm;

View File

@ -0,0 +1,71 @@
import type React from "react";
import type { ControllerRenderProps, FieldValues } from "react-hook-form";
import type * as z from "zod";
import type { INPUT_COMPONENTS } from "./config";
export type FieldConfigItem = {
description?: React.ReactNode;
inputProps?: React.InputHTMLAttributes<HTMLInputElement> & {
showLabel?: boolean;
};
label?: string;
fieldType?: keyof typeof INPUT_COMPONENTS | React.FC<AutoFormInputComponentProps>;
renderParent?: (props: {
children: React.ReactNode;
}) => React.ReactElement | null;
};
export type FieldConfig<SchemaType extends z.infer<z.ZodObject<any, any>>> = {
// If SchemaType.key is an object, create a nested FieldConfig, otherwise FieldConfigItem
[Key in keyof SchemaType]?: SchemaType[Key] extends object
? FieldConfig<z.infer<SchemaType[Key]>>
: FieldConfigItem;
};
export enum DependencyType {
DISABLES = 0,
REQUIRES = 1,
HIDES = 2,
SETS_OPTIONS = 3,
}
type BaseDependency<SchemaType extends z.infer<z.ZodObject<any, any>>> = {
sourceField: keyof SchemaType;
type: DependencyType;
targetField: keyof SchemaType;
when: (sourceFieldValue: any, targetFieldValue: any) => boolean;
};
export type ValueDependency<SchemaType extends z.infer<z.ZodObject<any, any>>> =
BaseDependency<SchemaType> & {
type: DependencyType.DISABLES | DependencyType.REQUIRES | DependencyType.HIDES;
};
export type EnumValues = readonly [string, ...string[]];
export type OptionsDependency<SchemaType extends z.infer<z.ZodObject<any, any>>> =
BaseDependency<SchemaType> & {
type: DependencyType.SETS_OPTIONS;
// Partial array of values from sourceField that will trigger the dependency
options: EnumValues;
};
export type Dependency<SchemaType extends z.infer<z.ZodObject<any, any>>> =
| ValueDependency<SchemaType>
| OptionsDependency<SchemaType>;
/**
* A FormInput component can handle a specific Zod type (e.g. "ZodBoolean")
*/
export type AutoFormInputComponentProps = {
zodInputProps: React.InputHTMLAttributes<HTMLInputElement>;
field: ControllerRenderProps<FieldValues, any>;
fieldConfigItem: FieldConfigItem;
label: string;
isRequired: boolean;
fieldProps: any;
zodItem: z.ZodAny;
className?: string;
};

View File

@ -0,0 +1,167 @@
import type React from "react";
import type { DefaultValues } from "react-hook-form";
import type { z } from "zod";
import type { FieldConfig } from "./types";
// TODO: This should support recursive ZodEffects but TypeScript doesn't allow circular type definitions.
export type ZodObjectOrWrapped =
| z.ZodObject<any, any>
| z.ZodEffects<z.ZodObject<any, any>>;
/**
* Beautify a camelCase string.
* e.g. "myString" -> "My String"
*/
export function beautifyObjectName(string: string) {
// if numbers only return the string
let output = string.replace(/([A-Z])/g, " $1");
output = output.charAt(0).toUpperCase() + output.slice(1);
return output;
}
/**
* Get the lowest level Zod type.
* This will unpack optionals, refinements, etc.
*/
export function getBaseSchema<ChildType extends z.ZodAny | z.AnyZodObject = z.ZodAny>(
schema: ChildType | z.ZodEffects<ChildType>,
): ChildType | null {
if (!schema) return null;
if ("innerType" in schema._def) {
return getBaseSchema(schema._def.innerType as ChildType);
}
if ("schema" in schema._def) {
return getBaseSchema(schema._def.schema as ChildType);
}
return schema as ChildType;
}
/**
* Get the type name of the lowest level Zod type.
* This will unpack optionals, refinements, etc.
*/
export function getBaseType(schema: z.ZodAny): string {
const baseSchema = getBaseSchema(schema);
return baseSchema ? baseSchema._def.typeName : "";
}
/**
* Search for a "ZodDefult" in the Zod stack and return its value.
*/
export function getDefaultValueInZodStack(schema: z.ZodAny): any {
const typedSchema = schema as unknown as z.ZodDefault<z.ZodNumber | z.ZodString>;
if (typedSchema._def.typeName === "ZodDefault") {
return typedSchema._def.defaultValue();
}
if ("innerType" in typedSchema._def) {
return getDefaultValueInZodStack(typedSchema._def.innerType as unknown as z.ZodAny);
}
if ("schema" in typedSchema._def) {
return getDefaultValueInZodStack((typedSchema._def as any).schema as z.ZodAny);
}
return undefined;
}
/**
* Get all default values from a Zod schema.
*/
export function getDefaultValues<Schema extends z.ZodObject<any, any>>(
schema: Schema,
fieldConfig?: FieldConfig<z.infer<Schema>>,
) {
if (!schema) return null;
const { shape } = schema;
type DefaultValuesType = DefaultValues<Partial<z.infer<Schema>>>;
const defaultValues = {} as DefaultValuesType;
if (!shape) return defaultValues;
for (const key of Object.keys(shape)) {
const item = shape[key] as z.ZodAny;
if (getBaseType(item) === "ZodObject") {
const defaultItems = getDefaultValues(
getBaseSchema(item) as unknown as z.ZodObject<any, any>,
fieldConfig?.[key] as FieldConfig<z.infer<Schema>>,
);
if (defaultItems !== null) {
for (const defaultItemKey of Object.keys(defaultItems)) {
const pathKey = `${key}.${defaultItemKey}` as keyof DefaultValuesType;
defaultValues[pathKey] = defaultItems[defaultItemKey];
}
}
} else {
let defaultValue = getDefaultValueInZodStack(item);
if (
(defaultValue === null || defaultValue === "") &&
fieldConfig?.[key]?.inputProps
) {
defaultValue = (fieldConfig?.[key]?.inputProps as unknown as any).defaultValue;
}
if (defaultValue !== undefined) {
defaultValues[key as keyof DefaultValuesType] = defaultValue;
}
}
}
return defaultValues;
}
export function getObjectFormSchema(schema: ZodObjectOrWrapped): z.ZodObject<any, any> {
if (schema?._def.typeName === "ZodEffects") {
const typedSchema = schema as z.ZodEffects<z.ZodObject<any, any>>;
return getObjectFormSchema(typedSchema._def.schema);
}
return schema as z.ZodObject<any, any>;
}
/**
* Convert a Zod schema to HTML input props to give direct feedback to the user.
* Once submitted, the schema will be validated completely.
*/
export function zodToHtmlInputProps(
schema: z.ZodNumber | z.ZodString | z.ZodOptional<z.ZodNumber | z.ZodString> | any,
): React.InputHTMLAttributes<HTMLInputElement> {
if (["ZodOptional", "ZodNullable"].includes(schema._def.typeName)) {
const typedSchema = schema as z.ZodOptional<z.ZodNumber | z.ZodString>;
return {
...zodToHtmlInputProps(typedSchema._def.innerType),
required: false,
};
}
const typedSchema = schema as z.ZodNumber | z.ZodString;
if (!("checks" in typedSchema._def))
return {
required: true,
};
const { checks } = typedSchema._def;
const inputProps: React.InputHTMLAttributes<HTMLInputElement> = {
required: true,
};
const type = getBaseType(schema);
for (const check of checks) {
if (check.kind === "min") {
if (type === "ZodString") {
inputProps.minLength = check.value;
} else {
inputProps.min = check.value;
}
}
if (check.kind === "max") {
if (type === "ZodString") {
inputProps.maxLength = check.value;
} else {
inputProps.max = check.value;
}
}
}
return inputProps;
}

View File

@ -0,0 +1,193 @@
// @flow
import * as React from 'react';
import {
Dialog,
DialogContent,
DialogTrigger,
} from "@/components/ui/dialog";
import {Button} from "@/components/ui/button";
import {Ban, DollarSign, RefreshCw} from "lucide-react";
import * as z from "zod";
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs";
import {Dispatch, SetStateAction, useEffect, useState} from "react";
import type {ICryptoInWalletInfo} from "@/interfaces/crypto.interface";
import {toast} from "@/components/ui/use-toast";
import AutoForm, {AutoFormSubmit} from "@/components/auto-form";
import type {IApiAllOffersRes, IApiDoTradeReq} from "@/interfaces/api.interface";
import ApiRequest from "@/services/apiRequest";
import {Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form";
import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from "@/components/ui/select";
import Link from "next/link";
import {useForm} from "react-hook-form";
import {zodResolver} from "@hookform/resolvers/zod";
type Props = {
cryptoData: ICryptoInWalletInfo
};
export function BuyModal(props: Props) {
const [isLoading, setIsLoading] = useState<boolean>(false)
const [offersList, setOffersList] = useState<IApiAllOffersRes[]>([])
const [isLoaded,setIsLoaded] = useState(false)
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
if(!isLoaded) {
ApiRequest.authenticated.get.json<IApiAllOffersRes[]>(`offer/crypto/${props.cryptoData.id}`).then((response) => {
setOffersList(response.data);
console.log(`Crypto ${props.cryptoData.name} -> ${response.data.length}`)
setIsLoaded(true);
return;
})
}
}, [isLoaded]);
const buyFromServerSchema = z.object({
amount: z
.number({
required_error: "An amount is needed.",
})
.min(1)
.max(props.cryptoData.quantity, "You cant buy more that what is available on the server.")
.describe("The amount you want to buy."),
});
const buyFromUserSchema = z.object({
offerId: z
.string({
required_error: "You should select an offer.",
})
.uuid(),
})
function onBuyFromServerSubmit(data: z.infer<typeof buyFromServerSchema>) {
ApiRequest.authenticated.post.json("crypto/buy", {id_crypto: props.cryptoData.id, amount: data.amount}).then((res)=>{
if (res.status !== 201) {
toast({
title: "An error occurred !",
description: (<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white text-wrap">{JSON.stringify(res.statusText, null, 2)}</code>
</pre>)
})
return
}
toast({
title: "Transaction accepted.",
description: (
<p>You will be redirected.</p>
)
})
setTimeout(()=>location.reload(), 1_500)
})
toast({
title: "You submitted the following values:",
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
</pre>
),
})
}
function onBuyFromUserSubmit(data: z.infer<typeof buyFromUserSchema>) {
ApiRequest.authenticated.post.json("trade/create", {id_offer: data.offerId}).then((res)=>{
if (res.status !== 201) {
toast({
title: "An error occurred !",
description: (<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white text-wrap">{JSON.stringify(res.statusText, null, 2)}</code>
</pre>)
})
return
}
toast({
title: "Transaction accepted.",
description: (
<p>You will be redirected.</p>
)
})
setTimeout(()=>location.reload(), 1_500)
})
toast({
title: "You submitted the following values:",
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
</pre>
),
})
}
const buyFromUserForm = useForm<z.infer<typeof buyFromUserSchema>>({
resolver: zodResolver(buyFromUserSchema),
})
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="light"><DollarSign /></Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[800px] flex flex-col justify-start items-center">
<Tabs defaultValue="server" className="w-full p-2 my-4">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="user">Buy from user <p className={" ml-1 px-1 bg-primary text-primary-foreground rounded"}>{offersList.length}</p></TabsTrigger>
<TabsTrigger value="server">Buy from server</TabsTrigger>
</TabsList>
<TabsContent value="user" className={"flex flex-col justify-start items-center"}>
<Form {...buyFromUserForm}>
<form onSubmit={buyFromUserForm.handleSubmit(onBuyFromUserSubmit)} className="w-full space-y-6">
<FormField
control={buyFromUserForm.control}
name="offerId"
render={({ field }) => (
<FormItem>
<FormLabel>Select an offer</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select an offer to purchase." />
</SelectTrigger>
</FormControl>
<SelectContent>
{offersList.map((offer)=>{
return (
<SelectItem value={offer.id} key={offer.id}>{`${offer.amount}x ${offer.Crypto.name} - ${offer.User.pseudo}`}</SelectItem>
)
})}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
</TabsContent>
<TabsContent value="server" className={"flex flex-col justify-start items-center"}>
{!props.cryptoData.quantity && <div
className={"bg-destructive text-destructive-foreground border-destructive rounded p-2 flex justify-start items-center gap-2"}>
<Ban/>
<p>The server dont have stock for the designated cryptos.</p>
</div>}
<div className={"w-full flex justify-center gap-2 items-center p-2"}>
<p>Available quantity on the server :</p>
<p className={"p-1 bg-accent text-accent-foreground rounded m-1"}>{props.cryptoData.quantity}</p>
</div>
{props.cryptoData.quantity && <AutoForm
// Pass the schema to the form
formSchema={buyFromServerSchema}
onSubmit={onBuyFromServerSubmit}
>
<AutoFormSubmit
disabled={!!isLoading}
className={"gap-2 disabled:bg-secondary-foreground"}
>
{/* biome-ignore lint/style/useTemplate: <explanation> */}
<RefreshCw className={`animate-spin ${!isLoading && "hidden"}`}/>
<p>Buy from the server</p>
</AutoFormSubmit>
</AutoForm>}
</TabsContent>
</Tabs>
</DialogContent>
</Dialog>
);
};

View File

@ -0,0 +1,30 @@
// @flow
import * as React from 'react';
import {
Dialog,
DialogContent,
DialogDescription, DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {Button} from "@/components/ui/button";
import {Label} from "@/components/ui/label";
import {Input} from "@/components/ui/input";
import {LineChart} from "lucide-react";
type Props = {
targetedCryptoId: string
};
export function ViewModal(props: Props) {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="ghost"><LineChart /></Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[800px]">
Test 1,2 !
//From here
</DialogContent>
</Dialog>
);
};

View File

@ -0,0 +1,86 @@
'use client'
import * as React from 'react';
import type {ICryptoInWalletInfo} from "@/interfaces/crypto.interface";
import {
type ColumnDef,
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table"
import {DataTable} from "@/components/ui/data-table";
import {Button} from "@/components/ui/button";
import {ArrowUpDown, MoreHorizontal} from "lucide-react";
import {useRouter} from "next/navigation";
import {useState} from "react";
import {BuyModal} from "@/components/cryptos/buy-modal";
import {ViewModal} from "@/components/cryptos/view-modal";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
}
type Props = {
cryptosArray: ICryptoInWalletInfo[]
};
export function CryptosTable(props: Props) {
const router = useRouter()
const cryptos= props.cryptosArray
const columns: ColumnDef<ICryptoInWalletInfo, any>[] = [
{
accessorKey: "name",
header: "Name",
},
{
accessorKey: "value",
header: ({ column }) => {
return (
<Button
variant="light"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Value - USD
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
)
},
},
{
accessorKey: "quantity",
header: ({ column }) => {
return (
<Button
variant="light"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Available from server
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
)
},
},
{
id: "actions",
cell: ({ row }) => {
const payment = row.original
return (
<div className={"flex gap-2"}>
<BuyModal cryptoData={row.original}/>
<ViewModal targetedCryptoId={row.original.id}/>
</div>
)
},
},
];
return (
<>
<DataTable columns={columns} data={cryptos} fieldToFilter={"name"}/>
</>
);
}

View File

@ -0,0 +1,86 @@
'use client'
import * as React from 'react';
import type {ICryptoInUserWalletInfo, ICryptoInWalletInfo} from "@/interfaces/crypto.interface";
import {
type ColumnDef,
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table"
import {DataTable} from "@/components/ui/data-table";
import {Button} from "@/components/ui/button";
import {ArrowUpDown, MoreHorizontal} from "lucide-react";
import {useRouter} from "next/navigation";
import {useState} from "react";
import {BuyModal} from "@/components/cryptos/buy-modal";
import {ViewModal} from "@/components/cryptos/view-modal";
import {IUserWallet} from "@/interfaces/userdata.interface";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
}
type Props = {
walletArray: IUserWallet
};
export function WalletTable(props: Props) {
const router = useRouter()
const wallet= props.walletArray.owned_cryptos
const columns: ColumnDef<ICryptoInUserWalletInfo, any>[] = [
{
accessorKey: "name",
header: "Name",
},
{
accessorKey: "value",
header: ({ column }) => {
return (
<Button
variant="light"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Value - USD
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
)
},
},
{
accessorKey: "owned_amount",
header: ({ column }) => {
return (
<Button
variant="light"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Amount owned
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
)
},
},
{
id: "actions",
cell: ({ row }) => {
const payment = row.original
return (
<div className={"flex gap-2"}>
<p className={"font-light italic text-xs"}>Soon here : Sell, History</p>
</div>
)
},
},
];
return (
<>
<DataTable columns={columns} data={wallet} fieldToFilter={"name"}/>
</>
);
}

View File

@ -1,33 +1,55 @@
import {Copyright} from "lucide-react"; import { Copyright } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
export function Footer() { export function Footer() {
return ( return (
<footer className={"flex flex-col-reverse md:flex-row justify-between gap-2 md:gap-1 items-center p-2 border-t-2 w-full"}> <footer
<div className={"flex flex-row gap-1 items-center justify-center md:justify-start md:w-1/3 opacity-50"}> className={
<Copyright className={"w-4"}/> "flex flex-col-reverse md:flex-row justify-between gap-2 md:gap-1 self-end order-6 items-center p-2 border-t-2 w-full"
}
>
<div
className={
"flex flex-row gap-1 items-center justify-center md:justify-start md:w-1/3 opacity-50"
}
>
<Copyright className={"w-4"} />
<h4 className={"pr-2"}>Yidhra Studio</h4> <h4 className={"pr-2"}>Yidhra Studio</h4>
<p>MIT <em>2024</em></p> <p>
MIT <em>2024</em>
</p>
</div> </div>
<div className={"flex flex-col flex-wrap md:flex-row max-h-24 md:flex-nowrap gap-1 md:gap-2 items-center justify-evenly w-full md:w-1/3"}> <div
<Link href={"#"} className={"p-1 hover:-translate-y-1.5 hover:text-primary w-1/2"}> className={
"flex flex-col flex-wrap md:flex-row max-h-24 md:flex-nowrap gap-1 md:gap-2 items-center justify-evenly w-full md:w-1/3"
}
>
<Link
href={"#"}
className={"p-1 hover:-translate-y-1.5 hover:text-primary w-1/2"}
>
<h3 className={"text-nowrap text-center"}>Data privacy</h3> <h3 className={"text-nowrap text-center"}>Data privacy</h3>
</Link> </Link>
<Link href={"#"} className={"p-1 hover:-translate-y-1.5 hover:text-primary w-1/2"}> <Link
href={"#"}
className={"p-1 hover:-translate-y-1.5 hover:text-primary w-1/2"}
>
<h3 className={"text-nowrap text-center"}>Terms and conditions</h3> <h3 className={"text-nowrap text-center"}>Terms and conditions</h3>
</Link> </Link>
<Link href={"#"} className={"p-1 hover:-translate-y-1.5 hover:text-primary w-1/2"}> <Link
href={"#"}
className={"p-1 hover:-translate-y-1.5 hover:text-primary w-1/2"}
>
<h3 className={"text-nowrap text-center"}>Legal notice</h3> <h3 className={"text-nowrap text-center"}>Legal notice</h3>
</Link> </Link>
<Link href={"#"} className={"p-1 hover:-translate-y-1.5 hover:text-primary w-1/2"}> <Link
href={"#"}
className={"p-1 hover:-translate-y-1.5 hover:text-primary w-1/2"}
>
<h3 className={"text-nowrap text-center"}>Support Center</h3> <h3 className={"text-nowrap text-center"}>Support Center</h3>
</Link> </Link>
</div> </div>
<div className={"flex flex-row gap-1 items-center justify-center md:justify-end md:w-1/3"}> <div />
</div>
</footer> </footer>
) );
} }

View File

@ -1,24 +1,41 @@
import { AccountDialog } from "@/components/account-dialog";
import { ThemeBtnSelector } from "@/components/theme-btn-selector";
import Image from "next/image"; import Image from "next/image";
import React from "react"; import type React from "react";
import {ThemeBtnSelector} from "@/components/theme-btn-selector";
export function Header({title, children}: {title?: string, children?: React.ReactNode}) {
export function Header({
title,
children,
}: { title?: string; children?: React.ReactNode }) {
return ( return (
<header className={"flex flex-col md:flex-row justify-between items-center w-full p-1 md:px-3 md:py-2 border-b-2"}> <header
<div className={"flex flex-row justify-center md:justify-start items-center gap-2 md:w-1/3"}> className={
<Image src={'yellow-bit.svg'} alt={'Logo of YeloBit'} width={42} height={42}/> "flex flex-col md:flex-row justify-between items-center w-full p-1 md:px-3 md:py-2 pb-2 border-b-2"
<h1 className={"font-bold text-xl align-middle text-center text-wrap"}>{title || 'YeloBit'}</h1> }
>
<div
className={
"flex flex-row justify-center md:justify-start items-center gap-2 md:w-1/3"
}
>
<Image src={"yellow-bit.svg"} alt={"Logo of YeloBit"} width={42} height={42} />
<h1 className={"font-bold text-xl align-middle text-center text-wrap"}>
{title || "YeloBit"}
</h1>
</div> </div>
<div className={"w-1/3 flex flex-row justify-center items-center"}> <div
className={"w-1/3 flex flex-col md:flex-row w-full justify-center items-center"}
>
{children} {children}
</div> </div>
<div className={"w-1/3 flex flex-row justify-end items-center"}> <div
<ThemeBtnSelector/> className={
"w-1/3 flex flex-row justify-center md:justify-end w-full md:w-fit gap-2 items-center"
}
>
<AccountDialog />
<ThemeBtnSelector />
</div> </div>
</header> </header>
) );
} }

View File

@ -1,9 +1,8 @@
"use client" "use client";
import * as React from "react" import Link from "next/link";
import Link from "next/link" import * as React from "react";
import { cn } from "@/lib/utils"
import { import {
NavigationMenu, NavigationMenu,
NavigationMenuContent, NavigationMenuContent,
@ -12,9 +11,10 @@ import {
NavigationMenuList, NavigationMenuList,
NavigationMenuTrigger, NavigationMenuTrigger,
navigationMenuTriggerStyle, navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu" } from "@/components/ui/navigation-menu";
import { cn } from "@/lib/utils";
import { Boxes, Info } from "lucide-react";
import Image from "next/image"; import Image from "next/image";
import {Boxes, Info} from "lucide-react";
const components: { title: string; href: string; description: string }[] = [ const components: { title: string; href: string; description: string }[] = [
{ {
@ -26,8 +26,7 @@ const components: { title: string; href: string; description: string }[] = [
{ {
title: "Hover Card", title: "Hover Card",
href: "/docs/primitives/hover-card", href: "/docs/primitives/hover-card",
description: description: "For sighted users to preview content available behind a link.",
"For sighted users to preview content available behind a link.",
}, },
{ {
title: "Progress", title: "Progress",
@ -52,14 +51,17 @@ const components: { title: string; href: string; description: string }[] = [
description: description:
"A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.", "A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.",
}, },
] ];
export function PrimaryNavigationMenu() { export function PrimaryNavigationMenu() {
return ( return (
<NavigationMenu> <NavigationMenu>
<NavigationMenuList className={"flex flex-row flex-wrap md:flex-nowrap"}> <NavigationMenuList className={"flex flex-row flex-wrap md:flex-nowrap"}>
<NavigationMenuItem className={"relative"}> <NavigationMenuItem className={"relative"}>
<NavigationMenuTrigger className={"gap-1"}><Info className={"w-4"} />Getting started</NavigationMenuTrigger> <NavigationMenuTrigger className={"gap-1"}>
<Info className={"w-4"} />
Getting started
</NavigationMenuTrigger>
<NavigationMenuContent className={""}> <NavigationMenuContent className={""}>
<ul className="grid gap-3 p-6 md:w-[400px] lg:w-[500px] lg:grid-cols-[.75fr_1fr]"> <ul className="grid gap-3 p-6 md:w-[400px] lg:w-[500px] lg:grid-cols-[.75fr_1fr]">
<li className="row-span-3"> <li className="row-span-3">
@ -68,14 +70,16 @@ export function PrimaryNavigationMenu() {
className="flex h-full w-full select-none flex-col justify-end rounded-md bg-gradient-to-b from-muted/50 to-muted p-6 no-underline outline-none focus:shadow-md" className="flex h-full w-full select-none flex-col justify-end rounded-md bg-gradient-to-b from-muted/50 to-muted p-6 no-underline outline-none focus:shadow-md"
href="/" href="/"
> >
<Image src={'logo-red.svg'} alt={'Logo of Yidhra Studio'} width={64} height={64}/> <Image
<div className="mb-2 mt-4 text-lg font-medium"> src={"logo-red.svg"}
shadcn/ui alt={"Logo of Yidhra Studio"}
</div> width={64}
height={64}
/>
<div className="mb-2 mt-4 text-lg font-medium">shadcn/ui</div>
<p className="text-sm leading-tight text-muted-foreground"> <p className="text-sm leading-tight text-muted-foreground">
Beautifully designed components that you can copy and Beautifully designed components that you can copy and paste into
paste into your apps. Accessible. Customizable. Open your apps. Accessible. Customizable. Open Source.
Source.
</p> </p>
</a> </a>
</NavigationMenuLink> </NavigationMenuLink>
@ -93,7 +97,10 @@ export function PrimaryNavigationMenu() {
</NavigationMenuContent> </NavigationMenuContent>
</NavigationMenuItem> </NavigationMenuItem>
<NavigationMenuItem> <NavigationMenuItem>
<NavigationMenuTrigger className={"gap-1"}><Boxes className={"w-4"} />Features</NavigationMenuTrigger> <NavigationMenuTrigger className={"gap-1"}>
<Boxes className={"w-4"} />
Features
</NavigationMenuTrigger>
<NavigationMenuContent> <NavigationMenuContent>
<ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] "> <ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] ">
{components.map((component) => ( {components.map((component) => (
@ -117,7 +124,7 @@ export function PrimaryNavigationMenu() {
</NavigationMenuItem> </NavigationMenuItem>
</NavigationMenuList> </NavigationMenuList>
</NavigationMenu> </NavigationMenu>
) );
} }
const ListItem = React.forwardRef< const ListItem = React.forwardRef<
@ -131,7 +138,7 @@ const ListItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground", "block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
className className,
)} )}
{...props} {...props}
> >
@ -142,6 +149,6 @@ const ListItem = React.forwardRef<
</a> </a>
</NavigationMenuLink> </NavigationMenuLink>
</li> </li>
) );
}) });
ListItem.displayName = "ListItem" ListItem.displayName = "ListItem";

View File

@ -0,0 +1,14 @@
"use client";
import { Footer } from "@/components/footer";
import { Header } from "@/components/header";
import { ThemeProvider } from "@/components/providers/theme-provider";
import { UserDataProvider } from "@/components/providers/userdata-provider";
import type React from "react";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<UserDataProvider>{children}</UserDataProvider>
</ThemeProvider>
);
}

View File

@ -1,9 +1,9 @@
"use client" "use client";
import * as React from "react" import { ThemeProvider as NextThemesProvider } from "next-themes";
import { ThemeProvider as NextThemesProvider } from "next-themes" import type { ThemeProviderProps } from "next-themes/dist/types";
import { type ThemeProviderProps } from "next-themes/dist/types" import * as React from "react";
export function ThemeProvider({ children, ...props }: ThemeProviderProps) { export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider> return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
} }

View File

@ -0,0 +1,25 @@
import type { IUserData } from "@/interfaces/userdata.interface";
import { useEncodedLocalStorage } from "@/services/localStorage";
import React from "react";
export interface IUserDataProvider {
userData: IUserData | undefined;
setUserData: React.Dispatch<React.SetStateAction<IUserData | undefined>>;
}
export const UserDataContext = React.createContext<IUserDataProvider | undefined>(
undefined,
);
export const UserDataProvider = ({ children }: { children: React.ReactNode }) => {
const [userData, setUserData] = useEncodedLocalStorage<IUserData | undefined>(
"user_data",
undefined,
);
return (
<UserDataContext.Provider value={{ userData, setUserData }}>
{children}
</UserDataContext.Provider>
);
};

View File

@ -1,19 +1,19 @@
"use client" "use client";
import * as React from "react" import { MoonStar, Sun } from "lucide-react";
import {MoonStar, Sun} from "lucide-react"; import { useTheme } from "next-themes";
import { useTheme } from "next-themes" import * as React from "react";
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu" } from "@/components/ui/dropdown-menu";
export function ThemeBtnSelector() { export function ThemeBtnSelector() {
const { setTheme } = useTheme() const { setTheme } = useTheme();
return ( return (
<DropdownMenu> <DropdownMenu>
@ -25,16 +25,10 @@ export function ThemeBtnSelector() {
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}> <DropdownMenuItem onClick={() => setTheme("light")}>Light</DropdownMenuItem>
Light <DropdownMenuItem onClick={() => setTheme("dark")}>Dark</DropdownMenuItem>
</DropdownMenuItem> <DropdownMenuItem onClick={() => setTheme("system")}>System</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
System
</DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
) );
} }

View File

@ -1,24 +1,20 @@
"use client" "use client";
import * as React from "react" import * as AccordionPrimitive from "@radix-ui/react-accordion";
import * as AccordionPrimitive from "@radix-ui/react-accordion" import { ChevronDown } from "lucide-react";
import { ChevronDown } from "lucide-react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Accordion = AccordionPrimitive.Root const Accordion = AccordionPrimitive.Root;
const AccordionItem = React.forwardRef< const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>, React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item> React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AccordionPrimitive.Item <AccordionPrimitive.Item ref={ref} className={cn("border-b", className)} {...props} />
ref={ref} ));
className={cn("border-b", className)} AccordionItem.displayName = "AccordionItem";
{...props}
/>
))
AccordionItem.displayName = "AccordionItem"
const AccordionTrigger = React.forwardRef< const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>, React.ElementRef<typeof AccordionPrimitive.Trigger>,
@ -29,7 +25,7 @@ const AccordionTrigger = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180", "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
className className,
)} )}
{...props} {...props}
> >
@ -37,8 +33,8 @@ const AccordionTrigger = React.forwardRef<
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" /> <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger> </AccordionPrimitive.Trigger>
</AccordionPrimitive.Header> </AccordionPrimitive.Header>
)) ));
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
const AccordionContent = React.forwardRef< const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>, React.ElementRef<typeof AccordionPrimitive.Content>,
@ -51,8 +47,8 @@ const AccordionContent = React.forwardRef<
> >
<div className={cn("pb-4 pt-0", className)}>{children}</div> <div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content> </AccordionPrimitive.Content>
)) ));
AccordionContent.displayName = AccordionPrimitive.Content.displayName AccordionContent.displayName = AccordionPrimitive.Content.displayName;
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };

View File

@ -1,16 +1,16 @@
"use client" "use client";
import * as React from "react" import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" import * as React from "react";
import { cn } from "@/lib/utils" import { buttonVariants } from "@/components/ui/button";
import { buttonVariants } from "@/components/ui/button" import { cn } from "@/lib/utils";
const AlertDialog = AlertDialogPrimitive.Root const AlertDialog = AlertDialogPrimitive.Root;
const AlertDialogTrigger = AlertDialogPrimitive.Trigger const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
const AlertDialogPortal = AlertDialogPrimitive.Portal const AlertDialogPortal = AlertDialogPrimitive.Portal;
const AlertDialogOverlay = React.forwardRef< const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>, React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
@ -19,13 +19,13 @@ const AlertDialogOverlay = React.forwardRef<
<AlertDialogPrimitive.Overlay <AlertDialogPrimitive.Overlay
className={cn( className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className className,
)} )}
{...props} {...props}
ref={ref} ref={ref}
/> />
)) ));
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
const AlertDialogContent = React.forwardRef< const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>, React.ElementRef<typeof AlertDialogPrimitive.Content>,
@ -37,27 +37,24 @@ const AlertDialogContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className className,
)} )}
{...props} {...props}
/> />
</AlertDialogPortal> </AlertDialogPortal>
)) ));
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
const AlertDialogHeader = ({ const AlertDialogHeader = ({
className, className,
...props ...props
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
className={cn( className={cn("flex flex-col space-y-2 text-center sm:text-left", className)}
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props} {...props}
/> />
) );
AlertDialogHeader.displayName = "AlertDialogHeader" AlertDialogHeader.displayName = "AlertDialogHeader";
const AlertDialogFooter = ({ const AlertDialogFooter = ({
className, className,
@ -66,12 +63,12 @@ const AlertDialogFooter = ({
<div <div
className={cn( className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className className,
)} )}
{...props} {...props}
/> />
) );
AlertDialogFooter.displayName = "AlertDialogFooter" AlertDialogFooter.displayName = "AlertDialogFooter";
const AlertDialogTitle = React.forwardRef< const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>, React.ElementRef<typeof AlertDialogPrimitive.Title>,
@ -82,8 +79,8 @@ const AlertDialogTitle = React.forwardRef<
className={cn("text-lg font-semibold", className)} className={cn("text-lg font-semibold", className)}
{...props} {...props}
/> />
)) ));
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
const AlertDialogDescription = React.forwardRef< const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>, React.ElementRef<typeof AlertDialogPrimitive.Description>,
@ -94,9 +91,8 @@ const AlertDialogDescription = React.forwardRef<
className={cn("text-sm text-muted-foreground", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)) ));
AlertDialogDescription.displayName = AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
AlertDialogPrimitive.Description.displayName
const AlertDialogAction = React.forwardRef< const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>, React.ElementRef<typeof AlertDialogPrimitive.Action>,
@ -107,8 +103,8 @@ const AlertDialogAction = React.forwardRef<
className={cn(buttonVariants(), className)} className={cn(buttonVariants(), className)}
{...props} {...props}
/> />
)) ));
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
const AlertDialogCancel = React.forwardRef< const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>, React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
@ -116,15 +112,11 @@ const AlertDialogCancel = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel <AlertDialogPrimitive.Cancel
ref={ref} ref={ref}
className={cn( className={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
buttonVariants({ variant: "outline" }),
"mt-2 sm:mt-0",
className
)}
{...props} {...props}
/> />
)) ));
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
export { export {
AlertDialog, AlertDialog,
@ -138,4 +130,4 @@ export {
AlertDialogDescription, AlertDialogDescription,
AlertDialogAction, AlertDialogAction,
AlertDialogCancel, AlertDialogCancel,
} };

View File

@ -1,7 +1,7 @@
import * as React from "react" import { type VariantProps, cva } from "class-variance-authority";
import { cva, type VariantProps } from "class-variance-authority" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const alertVariants = cva( const alertVariants = cva(
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
@ -16,8 +16,8 @@ const alertVariants = cva(
defaultVariants: { defaultVariants: {
variant: "default", variant: "default",
}, },
} },
) );
const Alert = React.forwardRef< const Alert = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -29,8 +29,8 @@ const Alert = React.forwardRef<
className={cn(alertVariants({ variant }), className)} className={cn(alertVariants({ variant }), className)}
{...props} {...props}
/> />
)) ));
Alert.displayName = "Alert" Alert.displayName = "Alert";
const AlertTitle = React.forwardRef< const AlertTitle = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
@ -41,19 +41,15 @@ const AlertTitle = React.forwardRef<
className={cn("mb-1 font-medium leading-none tracking-tight", className)} className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props} {...props}
/> />
)) ));
AlertTitle.displayName = "AlertTitle" AlertTitle.displayName = "AlertTitle";
const AlertDescription = React.forwardRef< const AlertDescription = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement> React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div <div ref={ref} className={cn("text-sm [&_p]:leading-relaxed", className)} {...props} />
ref={ref} ));
className={cn("text-sm [&_p]:leading-relaxed", className)} AlertDescription.displayName = "AlertDescription";
{...props}
/>
))
AlertDescription.displayName = "AlertDescription"
export { Alert, AlertTitle, AlertDescription } export { Alert, AlertTitle, AlertDescription };

View File

@ -1,7 +1,7 @@
"use client" "use client";
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
const AspectRatio = AspectRatioPrimitive.Root const AspectRatio = AspectRatioPrimitive.Root;
export { AspectRatio } export { AspectRatio };

View File

@ -1,9 +1,9 @@
"use client" "use client";
import * as React from "react" import * as AvatarPrimitive from "@radix-ui/react-avatar";
import * as AvatarPrimitive from "@radix-ui/react-avatar" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Avatar = React.forwardRef< const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>, React.ElementRef<typeof AvatarPrimitive.Root>,
@ -13,12 +13,12 @@ const Avatar = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className className,
)} )}
{...props} {...props}
/> />
)) ));
Avatar.displayName = AvatarPrimitive.Root.displayName Avatar.displayName = AvatarPrimitive.Root.displayName;
const AvatarImage = React.forwardRef< const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>, React.ElementRef<typeof AvatarPrimitive.Image>,
@ -29,8 +29,8 @@ const AvatarImage = React.forwardRef<
className={cn("aspect-square h-full w-full", className)} className={cn("aspect-square h-full w-full", className)}
{...props} {...props}
/> />
)) ));
AvatarImage.displayName = AvatarPrimitive.Image.displayName AvatarImage.displayName = AvatarPrimitive.Image.displayName;
const AvatarFallback = React.forwardRef< const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>, React.ElementRef<typeof AvatarPrimitive.Fallback>,
@ -40,11 +40,11 @@ const AvatarFallback = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted", "flex h-full w-full items-center justify-center rounded-full bg-muted",
className className,
)} )}
{...props} {...props}
/> />
)) ));
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
export { Avatar, AvatarImage, AvatarFallback } export { Avatar, AvatarImage, AvatarFallback };

View File

@ -1,7 +1,7 @@
import * as React from "react" import { type VariantProps, cva } from "class-variance-authority";
import { cva, type VariantProps } from "class-variance-authority" import type * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const badgeVariants = cva( const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
@ -20,17 +20,15 @@ const badgeVariants = cva(
defaultVariants: { defaultVariants: {
variant: "default", variant: "default",
}, },
} },
) );
export interface BadgeProps export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>, extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {} VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) { function Badge({ className, variant, ...props }: BadgeProps) {
return ( return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
} }
export { Badge, badgeVariants } export { Badge, badgeVariants };

View File

@ -1,16 +1,16 @@
import * as React from "react" import { Slot } from "@radix-ui/react-slot";
import { Slot } from "@radix-ui/react-slot" import { ChevronRight, MoreHorizontal } from "lucide-react";
import { ChevronRight, MoreHorizontal } from "lucide-react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Breadcrumb = React.forwardRef< const Breadcrumb = React.forwardRef<
HTMLElement, HTMLElement,
React.ComponentPropsWithoutRef<"nav"> & { React.ComponentPropsWithoutRef<"nav"> & {
separator?: React.ReactNode separator?: React.ReactNode;
} }
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />) >(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />);
Breadcrumb.displayName = "Breadcrumb" Breadcrumb.displayName = "Breadcrumb";
const BreadcrumbList = React.forwardRef< const BreadcrumbList = React.forwardRef<
HTMLOListElement, HTMLOListElement,
@ -20,12 +20,12 @@ const BreadcrumbList = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5", "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
className className,
)} )}
{...props} {...props}
/> />
)) ));
BreadcrumbList.displayName = "BreadcrumbList" BreadcrumbList.displayName = "BreadcrumbList";
const BreadcrumbItem = React.forwardRef< const BreadcrumbItem = React.forwardRef<
HTMLLIElement, HTMLLIElement,
@ -36,16 +36,16 @@ const BreadcrumbItem = React.forwardRef<
className={cn("inline-flex items-center gap-1.5", className)} className={cn("inline-flex items-center gap-1.5", className)}
{...props} {...props}
/> />
)) ));
BreadcrumbItem.displayName = "BreadcrumbItem" BreadcrumbItem.displayName = "BreadcrumbItem";
const BreadcrumbLink = React.forwardRef< const BreadcrumbLink = React.forwardRef<
HTMLAnchorElement, HTMLAnchorElement,
React.ComponentPropsWithoutRef<"a"> & { React.ComponentPropsWithoutRef<"a"> & {
asChild?: boolean asChild?: boolean;
} }
>(({ asChild, className, ...props }, ref) => { >(({ asChild, className, ...props }, ref) => {
const Comp = asChild ? Slot : "a" const Comp = asChild ? Slot : "a";
return ( return (
<Comp <Comp
@ -53,9 +53,9 @@ const BreadcrumbLink = React.forwardRef<
className={cn("transition-colors hover:text-foreground", className)} className={cn("transition-colors hover:text-foreground", className)}
{...props} {...props}
/> />
) );
}) });
BreadcrumbLink.displayName = "BreadcrumbLink" BreadcrumbLink.displayName = "BreadcrumbLink";
const BreadcrumbPage = React.forwardRef< const BreadcrumbPage = React.forwardRef<
HTMLSpanElement, HTMLSpanElement,
@ -69,8 +69,8 @@ const BreadcrumbPage = React.forwardRef<
className={cn("font-normal text-foreground", className)} className={cn("font-normal text-foreground", className)}
{...props} {...props}
/> />
)) ));
BreadcrumbPage.displayName = "BreadcrumbPage" BreadcrumbPage.displayName = "BreadcrumbPage";
const BreadcrumbSeparator = ({ const BreadcrumbSeparator = ({
children, children,
@ -85,13 +85,10 @@ const BreadcrumbSeparator = ({
> >
{children ?? <ChevronRight />} {children ?? <ChevronRight />}
</li> </li>
) );
BreadcrumbSeparator.displayName = "BreadcrumbSeparator" BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
const BreadcrumbEllipsis = ({ const BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<"span">) => (
className,
...props
}: React.ComponentProps<"span">) => (
<span <span
role="presentation" role="presentation"
aria-hidden="true" aria-hidden="true"
@ -101,8 +98,8 @@ const BreadcrumbEllipsis = ({
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More</span> <span className="sr-only">More</span>
</span> </span>
) );
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis" BreadcrumbEllipsis.displayName = "BreadcrumbElipssis";
export { export {
Breadcrumb, Breadcrumb,
@ -112,4 +109,4 @@ export {
BreadcrumbPage, BreadcrumbPage,
BreadcrumbSeparator, BreadcrumbSeparator,
BreadcrumbEllipsis, BreadcrumbEllipsis,
} };

View File

@ -1,8 +1,8 @@
import * as React from "react" import { Slot } from "@radix-ui/react-slot";
import { Slot } from "@radix-ui/react-slot" import { type VariantProps, cva } from "class-variance-authority";
import { cva, type VariantProps } from "class-variance-authority" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
@ -10,13 +10,12 @@ const buttonVariants = cva(
variants: { variants: {
variant: { variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90", default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground", "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground", ghost: "hover:bg-accent hover:text-accent-foreground",
light: "hover:bg-accent-foreground hover:text-accent",
link: "text-primary underline-offset-4 hover:underline", link: "text-primary underline-offset-4 hover:underline",
}, },
size: { size: {
@ -30,27 +29,27 @@ const buttonVariants = cva(
variant: "default", variant: "default",
size: "default", size: "default",
}, },
} },
) );
export interface ButtonProps export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>, extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> { VariantProps<typeof buttonVariants> {
asChild?: boolean asChild?: boolean;
} }
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => { ({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button" const Comp = asChild ? Slot : "button";
return ( return (
<Comp <Comp
className={cn(buttonVariants({ variant, size, className }))} className={cn(buttonVariants({ variant, size, className }))}
ref={ref} ref={ref}
{...props} {...props}
/> />
) );
} },
) );
Button.displayName = "Button" Button.displayName = "Button";
export { Button, buttonVariants } export { Button, buttonVariants };

View File

@ -1,13 +1,13 @@
"use client" "use client";
import * as React from "react" import { ChevronLeft, ChevronRight } from "lucide-react";
import { ChevronLeft, ChevronRight } from "lucide-react" import type * as React from "react";
import { DayPicker } from "react-day-picker" import { DayPicker } from "react-day-picker";
import { cn } from "@/lib/utils" import { buttonVariants } from "@/components/ui/button";
import { buttonVariants } from "@/components/ui/button" import { cn } from "@/lib/utils";
export type CalendarProps = React.ComponentProps<typeof DayPicker> export type CalendarProps = React.ComponentProps<typeof DayPicker>;
function Calendar({ function Calendar({
className, className,
@ -27,19 +27,18 @@ function Calendar({
nav: "space-x-1 flex items-center", nav: "space-x-1 flex items-center",
nav_button: cn( nav_button: cn(
buttonVariants({ variant: "outline" }), buttonVariants({ variant: "outline" }),
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100" "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
), ),
nav_button_previous: "absolute left-1", nav_button_previous: "absolute left-1",
nav_button_next: "absolute right-1", nav_button_next: "absolute right-1",
table: "w-full border-collapse space-y-1", table: "w-full border-collapse space-y-1",
head_row: "flex", head_row: "flex",
head_cell: head_cell: "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
"text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
row: "flex w-full mt-2", row: "flex w-full mt-2",
cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20", cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
day: cn( day: cn(
buttonVariants({ variant: "ghost" }), buttonVariants({ variant: "ghost" }),
"h-9 w-9 p-0 font-normal aria-selected:opacity-100" "h-9 w-9 p-0 font-normal aria-selected:opacity-100",
), ),
day_range_end: "day-range-end", day_range_end: "day-range-end",
day_selected: day_selected:
@ -48,8 +47,7 @@ function Calendar({
day_outside: day_outside:
"day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30", "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
day_disabled: "text-muted-foreground opacity-50", day_disabled: "text-muted-foreground opacity-50",
day_range_middle: day_range_middle: "aria-selected:bg-accent aria-selected:text-accent-foreground",
"aria-selected:bg-accent aria-selected:text-accent-foreground",
day_hidden: "invisible", day_hidden: "invisible",
...classNames, ...classNames,
}} }}
@ -59,8 +57,8 @@ function Calendar({
}} }}
{...props} {...props}
/> />
) );
} }
Calendar.displayName = "Calendar" Calendar.displayName = "Calendar";
export { Calendar } export { Calendar };

View File

@ -1,33 +1,31 @@
import * as React from "react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Card = React.forwardRef< const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
HTMLDivElement, ({ className, ...props }, ref) => (
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
className={cn( className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm", "rounded-lg border bg-card text-card-foreground shadow-sm",
className className,
)} )}
{...props} {...props}
/> />
)) ),
Card.displayName = "Card" );
Card.displayName = "Card";
const CardHeader = React.forwardRef< const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
HTMLDivElement, ({ className, ...props }, ref) => (
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)} className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props} {...props}
/> />
)) ),
CardHeader.displayName = "CardHeader" );
CardHeader.displayName = "CardHeader";
const CardTitle = React.forwardRef< const CardTitle = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
@ -35,45 +33,33 @@ const CardTitle = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<h3 <h3
ref={ref} ref={ref}
className={cn( className={cn("text-2xl font-semibold leading-none tracking-tight", className)}
"text-2xl font-semibold leading-none tracking-tight",
className
)}
{...props} {...props}
/> />
)) ));
CardTitle.displayName = "CardTitle" CardTitle.displayName = "CardTitle";
const CardDescription = React.forwardRef< const CardDescription = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement> React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<p <p ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
ref={ref} ));
className={cn("text-sm text-muted-foreground", className)} CardDescription.displayName = "CardDescription";
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef< const CardContent = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} /> <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
)) ));
CardContent.displayName = "CardContent" CardContent.displayName = "CardContent";
const CardFooter = React.forwardRef< const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
HTMLDivElement, ({ className, ...props }, ref) => (
React.HTMLAttributes<HTMLDivElement> <div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />
>(({ className, ...props }, ref) => ( ),
<div );
ref={ref} CardFooter.displayName = "CardFooter";
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };

View File

@ -1,45 +1,43 @@
"use client" "use client";
import * as React from "react" import useEmblaCarousel, { type UseEmblaCarouselType } from "embla-carousel-react";
import useEmblaCarousel, { import { ArrowLeft, ArrowRight } from "lucide-react";
type UseEmblaCarouselType, import * as React from "react";
} from "embla-carousel-react"
import { ArrowLeft, ArrowRight } from "lucide-react"
import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button";
import { Button } from "@/components/ui/button" import { cn } from "@/lib/utils";
type CarouselApi = UseEmblaCarouselType[1] type CarouselApi = UseEmblaCarouselType[1];
type UseCarouselParameters = Parameters<typeof useEmblaCarousel> type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
type CarouselOptions = UseCarouselParameters[0] type CarouselOptions = UseCarouselParameters[0];
type CarouselPlugin = UseCarouselParameters[1] type CarouselPlugin = UseCarouselParameters[1];
type CarouselProps = { type CarouselProps = {
opts?: CarouselOptions opts?: CarouselOptions;
plugins?: CarouselPlugin plugins?: CarouselPlugin;
orientation?: "horizontal" | "vertical" orientation?: "horizontal" | "vertical";
setApi?: (api: CarouselApi) => void setApi?: (api: CarouselApi) => void;
} };
type CarouselContextProps = { type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0] carouselRef: ReturnType<typeof useEmblaCarousel>[0];
api: ReturnType<typeof useEmblaCarousel>[1] api: ReturnType<typeof useEmblaCarousel>[1];
scrollPrev: () => void scrollPrev: () => void;
scrollNext: () => void scrollNext: () => void;
canScrollPrev: boolean canScrollPrev: boolean;
canScrollNext: boolean canScrollNext: boolean;
} & CarouselProps } & CarouselProps;
const CarouselContext = React.createContext<CarouselContextProps | null>(null) const CarouselContext = React.createContext<CarouselContextProps | null>(null);
function useCarousel() { function useCarousel() {
const context = React.useContext(CarouselContext) const context = React.useContext(CarouselContext);
if (!context) { if (!context) {
throw new Error("useCarousel must be used within a <Carousel />") throw new Error("useCarousel must be used within a <Carousel />");
} }
return context return context;
} }
const Carousel = React.forwardRef< const Carousel = React.forwardRef<
@ -47,78 +45,70 @@ const Carousel = React.forwardRef<
React.HTMLAttributes<HTMLDivElement> & CarouselProps React.HTMLAttributes<HTMLDivElement> & CarouselProps
>( >(
( (
{ { orientation = "horizontal", opts, setApi, plugins, className, children, ...props },
orientation = "horizontal", ref,
opts,
setApi,
plugins,
className,
children,
...props
},
ref
) => { ) => {
const [carouselRef, api] = useEmblaCarousel( const [carouselRef, api] = useEmblaCarousel(
{ {
...opts, ...opts,
axis: orientation === "horizontal" ? "x" : "y", axis: orientation === "horizontal" ? "x" : "y",
}, },
plugins plugins,
) );
const [canScrollPrev, setCanScrollPrev] = React.useState(false) const [canScrollPrev, setCanScrollPrev] = React.useState(false);
const [canScrollNext, setCanScrollNext] = React.useState(false) const [canScrollNext, setCanScrollNext] = React.useState(false);
const onSelect = React.useCallback((api: CarouselApi) => { const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) { if (!api) {
return return;
} }
setCanScrollPrev(api.canScrollPrev()) setCanScrollPrev(api.canScrollPrev());
setCanScrollNext(api.canScrollNext()) setCanScrollNext(api.canScrollNext());
}, []) }, []);
const scrollPrev = React.useCallback(() => { const scrollPrev = React.useCallback(() => {
api?.scrollPrev() api?.scrollPrev();
}, [api]) }, [api]);
const scrollNext = React.useCallback(() => { const scrollNext = React.useCallback(() => {
api?.scrollNext() api?.scrollNext();
}, [api]) }, [api]);
const handleKeyDown = React.useCallback( const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => { (event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") { if (event.key === "ArrowLeft") {
event.preventDefault() event.preventDefault();
scrollPrev() scrollPrev();
} else if (event.key === "ArrowRight") { } else if (event.key === "ArrowRight") {
event.preventDefault() event.preventDefault();
scrollNext() scrollNext();
} }
}, },
[scrollPrev, scrollNext] [scrollPrev, scrollNext],
) );
React.useEffect(() => { React.useEffect(() => {
if (!api || !setApi) { if (!api || !setApi) {
return return;
} }
setApi(api) setApi(api);
}, [api, setApi]) }, [api, setApi]);
React.useEffect(() => { React.useEffect(() => {
if (!api) { if (!api) {
return return;
} }
onSelect(api) onSelect(api);
api.on("reInit", onSelect) api.on("reInit", onSelect);
api.on("select", onSelect) api.on("select", onSelect);
return () => { return () => {
api?.off("select", onSelect) api?.off("select", onSelect);
} };
}, [api, onSelect]) }, [api, onSelect]);
return ( return (
<CarouselContext.Provider <CarouselContext.Provider
@ -126,8 +116,7 @@ const Carousel = React.forwardRef<
carouselRef, carouselRef,
api: api, api: api,
opts, opts,
orientation: orientation: orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
scrollPrev, scrollPrev,
scrollNext, scrollNext,
canScrollPrev, canScrollPrev,
@ -145,16 +134,16 @@ const Carousel = React.forwardRef<
{children} {children}
</div> </div>
</CarouselContext.Provider> </CarouselContext.Provider>
) );
} },
) );
Carousel.displayName = "Carousel" Carousel.displayName = "Carousel";
const CarouselContent = React.forwardRef< const CarouselContent = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
const { carouselRef, orientation } = useCarousel() const { carouselRef, orientation } = useCarousel();
return ( return (
<div ref={carouselRef} className="overflow-hidden"> <div ref={carouselRef} className="overflow-hidden">
@ -163,20 +152,20 @@ const CarouselContent = React.forwardRef<
className={cn( className={cn(
"flex", "flex",
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col", orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
className className,
)} )}
{...props} {...props}
/> />
</div> </div>
) );
}) });
CarouselContent.displayName = "CarouselContent" CarouselContent.displayName = "CarouselContent";
const CarouselItem = React.forwardRef< const CarouselItem = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
const { orientation } = useCarousel() const { orientation } = useCarousel();
return ( return (
<div <div
@ -186,19 +175,19 @@ const CarouselItem = React.forwardRef<
className={cn( className={cn(
"min-w-0 shrink-0 grow-0 basis-full", "min-w-0 shrink-0 grow-0 basis-full",
orientation === "horizontal" ? "pl-4" : "pt-4", orientation === "horizontal" ? "pl-4" : "pt-4",
className className,
)} )}
{...props} {...props}
/> />
) );
}) });
CarouselItem.displayName = "CarouselItem" CarouselItem.displayName = "CarouselItem";
const CarouselPrevious = React.forwardRef< const CarouselPrevious = React.forwardRef<
HTMLButtonElement, HTMLButtonElement,
React.ComponentProps<typeof Button> React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => { >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollPrev, canScrollPrev } = useCarousel() const { orientation, scrollPrev, canScrollPrev } = useCarousel();
return ( return (
<Button <Button
@ -210,7 +199,7 @@ const CarouselPrevious = React.forwardRef<
orientation === "horizontal" orientation === "horizontal"
? "-left-12 top-1/2 -translate-y-1/2" ? "-left-12 top-1/2 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90", : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
className className,
)} )}
disabled={!canScrollPrev} disabled={!canScrollPrev}
onClick={scrollPrev} onClick={scrollPrev}
@ -219,15 +208,15 @@ const CarouselPrevious = React.forwardRef<
<ArrowLeft className="h-4 w-4" /> <ArrowLeft className="h-4 w-4" />
<span className="sr-only">Previous slide</span> <span className="sr-only">Previous slide</span>
</Button> </Button>
) );
}) });
CarouselPrevious.displayName = "CarouselPrevious" CarouselPrevious.displayName = "CarouselPrevious";
const CarouselNext = React.forwardRef< const CarouselNext = React.forwardRef<
HTMLButtonElement, HTMLButtonElement,
React.ComponentProps<typeof Button> React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => { >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollNext, canScrollNext } = useCarousel() const { orientation, scrollNext, canScrollNext } = useCarousel();
return ( return (
<Button <Button
@ -239,7 +228,7 @@ const CarouselNext = React.forwardRef<
orientation === "horizontal" orientation === "horizontal"
? "-right-12 top-1/2 -translate-y-1/2" ? "-right-12 top-1/2 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90", : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
className className,
)} )}
disabled={!canScrollNext} disabled={!canScrollNext}
onClick={scrollNext} onClick={scrollNext}
@ -248,9 +237,9 @@ const CarouselNext = React.forwardRef<
<ArrowRight className="h-4 w-4" /> <ArrowRight className="h-4 w-4" />
<span className="sr-only">Next slide</span> <span className="sr-only">Next slide</span>
</Button> </Button>
) );
}) });
CarouselNext.displayName = "CarouselNext" CarouselNext.displayName = "CarouselNext";
export { export {
type CarouselApi, type CarouselApi,
@ -259,4 +248,4 @@ export {
CarouselItem, CarouselItem,
CarouselPrevious, CarouselPrevious,
CarouselNext, CarouselNext,
} };

View File

@ -1,10 +1,10 @@
"use client" "use client";
import * as React from "react" import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox" import { Check } from "lucide-react";
import { Check } from "lucide-react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Checkbox = React.forwardRef< const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>, React.ElementRef<typeof CheckboxPrimitive.Root>,
@ -14,7 +14,7 @@ const Checkbox = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground", "peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className className,
)} )}
{...props} {...props}
> >
@ -24,7 +24,7 @@ const Checkbox = React.forwardRef<
<Check className="h-4 w-4" /> <Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator> </CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root> </CheckboxPrimitive.Root>
)) ));
Checkbox.displayName = CheckboxPrimitive.Root.displayName Checkbox.displayName = CheckboxPrimitive.Root.displayName;
export { Checkbox } export { Checkbox };

View File

@ -1,11 +1,11 @@
"use client" "use client";
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
const Collapsible = CollapsiblePrimitive.Root const Collapsible = CollapsiblePrimitive.Root;
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
export { Collapsible, CollapsibleTrigger, CollapsibleContent } export { Collapsible, CollapsibleTrigger, CollapsibleContent };

View File

@ -1,12 +1,12 @@
"use client" "use client";
import * as React from "react" import type { DialogProps } from "@radix-ui/react-dialog";
import { type DialogProps } from "@radix-ui/react-dialog" import { Command as CommandPrimitive } from "cmdk";
import { Command as CommandPrimitive } from "cmdk" import { Search } from "lucide-react";
import { Search } from "lucide-react" import * as React from "react";
import { cn } from "@/lib/utils" import { Dialog, DialogContent } from "@/components/ui/dialog";
import { Dialog, DialogContent } from "@/components/ui/dialog" import { cn } from "@/lib/utils";
const Command = React.forwardRef< const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>, React.ElementRef<typeof CommandPrimitive>,
@ -16,12 +16,12 @@ const Command = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground", "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
className className,
)} )}
{...props} {...props}
/> />
)) ));
Command.displayName = CommandPrimitive.displayName Command.displayName = CommandPrimitive.displayName;
interface CommandDialogProps extends DialogProps {} interface CommandDialogProps extends DialogProps {}
@ -34,8 +34,8 @@ const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
</Command> </Command>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
) );
} };
const CommandInput = React.forwardRef< const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>, React.ElementRef<typeof CommandPrimitive.Input>,
@ -47,14 +47,14 @@ const CommandInput = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50", "flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className className,
)} )}
{...props} {...props}
/> />
</div> </div>
)) ));
CommandInput.displayName = CommandPrimitive.Input.displayName CommandInput.displayName = CommandPrimitive.Input.displayName;
const CommandList = React.forwardRef< const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>, React.ElementRef<typeof CommandPrimitive.List>,
@ -65,22 +65,18 @@ const CommandList = React.forwardRef<
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)} className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
{...props} {...props}
/> />
)) ));
CommandList.displayName = CommandPrimitive.List.displayName CommandList.displayName = CommandPrimitive.List.displayName;
const CommandEmpty = React.forwardRef< const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>, React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty> React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => ( >((props, ref) => (
<CommandPrimitive.Empty <CommandPrimitive.Empty ref={ref} className="py-6 text-center text-sm" {...props} />
ref={ref} ));
className="py-6 text-center text-sm"
{...props}
/>
))
CommandEmpty.displayName = CommandPrimitive.Empty.displayName CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
const CommandGroup = React.forwardRef< const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>, React.ElementRef<typeof CommandPrimitive.Group>,
@ -90,13 +86,13 @@ const CommandGroup = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground", "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
className className,
)} )}
{...props} {...props}
/> />
)) ));
CommandGroup.displayName = CommandPrimitive.Group.displayName CommandGroup.displayName = CommandPrimitive.Group.displayName;
const CommandSeparator = React.forwardRef< const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>, React.ElementRef<typeof CommandPrimitive.Separator>,
@ -107,8 +103,8 @@ const CommandSeparator = React.forwardRef<
className={cn("-mx-1 h-px bg-border", className)} className={cn("-mx-1 h-px bg-border", className)}
{...props} {...props}
/> />
)) ));
CommandSeparator.displayName = CommandPrimitive.Separator.displayName CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
const CommandItem = React.forwardRef< const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>, React.ElementRef<typeof CommandPrimitive.Item>,
@ -118,13 +114,13 @@ const CommandItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50", "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50",
className className,
)} )}
{...props} {...props}
/> />
)) ));
CommandItem.displayName = CommandPrimitive.Item.displayName CommandItem.displayName = CommandPrimitive.Item.displayName;
const CommandShortcut = ({ const CommandShortcut = ({
className, className,
@ -132,15 +128,12 @@ const CommandShortcut = ({
}: React.HTMLAttributes<HTMLSpanElement>) => { }: React.HTMLAttributes<HTMLSpanElement>) => {
return ( return (
<span <span
className={cn( className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props} {...props}
/> />
) );
} };
CommandShortcut.displayName = "CommandShortcut" CommandShortcut.displayName = "CommandShortcut";
export { export {
Command, Command,
@ -152,4 +145,4 @@ export {
CommandItem, CommandItem,
CommandShortcut, CommandShortcut,
CommandSeparator, CommandSeparator,
} };

View File

@ -1,27 +1,27 @@
"use client" "use client";
import * as React from "react" import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu" import { Check, ChevronRight, Circle } from "lucide-react";
import { Check, ChevronRight, Circle } from "lucide-react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const ContextMenu = ContextMenuPrimitive.Root const ContextMenu = ContextMenuPrimitive.Root;
const ContextMenuTrigger = ContextMenuPrimitive.Trigger const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
const ContextMenuGroup = ContextMenuPrimitive.Group const ContextMenuGroup = ContextMenuPrimitive.Group;
const ContextMenuPortal = ContextMenuPrimitive.Portal const ContextMenuPortal = ContextMenuPrimitive.Portal;
const ContextMenuSub = ContextMenuPrimitive.Sub const ContextMenuSub = ContextMenuPrimitive.Sub;
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
const ContextMenuSubTrigger = React.forwardRef< const ContextMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>, React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & { React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, children, ...props }, ref) => ( >(({ className, inset, children, ...props }, ref) => (
<ContextMenuPrimitive.SubTrigger <ContextMenuPrimitive.SubTrigger
@ -29,15 +29,15 @@ const ContextMenuSubTrigger = React.forwardRef<
className={cn( className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground", "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
> >
{children} {children}
<ChevronRight className="ml-auto h-4 w-4" /> <ChevronRight className="ml-auto h-4 w-4" />
</ContextMenuPrimitive.SubTrigger> </ContextMenuPrimitive.SubTrigger>
)) ));
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
const ContextMenuSubContent = React.forwardRef< const ContextMenuSubContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubContent>, React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
@ -47,12 +47,12 @@ const ContextMenuSubContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className className,
)} )}
{...props} {...props}
/> />
)) ));
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
const ContextMenuContent = React.forwardRef< const ContextMenuContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Content>, React.ElementRef<typeof ContextMenuPrimitive.Content>,
@ -63,18 +63,18 @@ const ContextMenuContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className className,
)} )}
{...props} {...props}
/> />
</ContextMenuPrimitive.Portal> </ContextMenuPrimitive.Portal>
)) ));
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
const ContextMenuItem = React.forwardRef< const ContextMenuItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Item>, React.ElementRef<typeof ContextMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & { React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Item <ContextMenuPrimitive.Item
@ -82,12 +82,12 @@ const ContextMenuItem = React.forwardRef<
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
/> />
)) ));
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
const ContextMenuCheckboxItem = React.forwardRef< const ContextMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>, React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
@ -97,7 +97,7 @@ const ContextMenuCheckboxItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className,
)} )}
checked={checked} checked={checked}
{...props} {...props}
@ -109,9 +109,8 @@ const ContextMenuCheckboxItem = React.forwardRef<
</span> </span>
{children} {children}
</ContextMenuPrimitive.CheckboxItem> </ContextMenuPrimitive.CheckboxItem>
)) ));
ContextMenuCheckboxItem.displayName = ContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName;
ContextMenuPrimitive.CheckboxItem.displayName
const ContextMenuRadioItem = React.forwardRef< const ContextMenuRadioItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>, React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
@ -121,7 +120,7 @@ const ContextMenuRadioItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className,
)} )}
{...props} {...props}
> >
@ -132,13 +131,13 @@ const ContextMenuRadioItem = React.forwardRef<
</span> </span>
{children} {children}
</ContextMenuPrimitive.RadioItem> </ContextMenuPrimitive.RadioItem>
)) ));
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
const ContextMenuLabel = React.forwardRef< const ContextMenuLabel = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Label>, React.ElementRef<typeof ContextMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & { React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Label <ContextMenuPrimitive.Label
@ -146,12 +145,12 @@ const ContextMenuLabel = React.forwardRef<
className={cn( className={cn(
"px-2 py-1.5 text-sm font-semibold text-foreground", "px-2 py-1.5 text-sm font-semibold text-foreground",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
/> />
)) ));
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
const ContextMenuSeparator = React.forwardRef< const ContextMenuSeparator = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Separator>, React.ElementRef<typeof ContextMenuPrimitive.Separator>,
@ -162,8 +161,8 @@ const ContextMenuSeparator = React.forwardRef<
className={cn("-mx-1 my-1 h-px bg-border", className)} className={cn("-mx-1 my-1 h-px bg-border", className)}
{...props} {...props}
/> />
)) ));
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
const ContextMenuShortcut = ({ const ContextMenuShortcut = ({
className, className,
@ -171,15 +170,12 @@ const ContextMenuShortcut = ({
}: React.HTMLAttributes<HTMLSpanElement>) => { }: React.HTMLAttributes<HTMLSpanElement>) => {
return ( return (
<span <span
className={cn( className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props} {...props}
/> />
) );
} };
ContextMenuShortcut.displayName = "ContextMenuShortcut" ContextMenuShortcut.displayName = "ContextMenuShortcut";
export { export {
ContextMenu, ContextMenu,
@ -197,4 +193,4 @@ export {
ContextMenuSubContent, ContextMenuSubContent,
ContextMenuSubTrigger, ContextMenuSubTrigger,
ContextMenuRadioGroup, ContextMenuRadioGroup,
} };

View File

@ -0,0 +1,112 @@
"use client";
import type { DropdownMenuTriggerProps } from "@radix-ui/react-dropdown-menu";
import { CheckIcon, ClipboardIcon } from "lucide-react";
import { Button, type ButtonProps } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { cn } from "@/lib/utils";
import { useCallback, useEffect, useState } from "react";
interface CopyButtonProps extends ButtonProps {
value: string;
src?: string;
event?: Event["NONE"];
}
interface Value {
data: string;
title: string;
}
export async function copyToClipboardWithMeta(value: string) {
await window?.navigator.clipboard.writeText(value);
}
export function CopyButton({
value,
className,
src,
variant = "ghost",
event,
...props
}: CopyButtonProps) {
const [hasCopied, setHasCopied] = useState(false);
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
setTimeout(() => {
setHasCopied(false);
}, 2000);
}, [hasCopied]);
return (
<Button
size="icon"
variant={variant}
className={cn(
"relative z-10 h-6 w-6 text-zinc-50 hover:bg-zinc-700 hover:text-zinc-50 [&_svg]:size-3",
className,
)}
onClick={() => {
copyToClipboardWithMeta(value).then(() => setHasCopied(true));
}}
{...props}
>
<span className="sr-only">Copy</span>
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
</Button>
);
}
interface CopyMultipleChoiceButtonProps extends DropdownMenuTriggerProps {
values: Value[];
}
export function CopyMultipleChoiceButton({
values,
className,
...props
}: CopyMultipleChoiceButtonProps) {
const [hasCopied, setHasCopied] = useState(false);
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
setTimeout(() => {
setHasCopied(false);
}, 2000);
}, [hasCopied]);
const copyCommand = useCallback((value: string) => {
copyToClipboardWithMeta(value).then(() => setHasCopied(true));
}, []);
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="icon"
variant="ghost"
className={cn(
"relative z-10 h-6 w-6 text-zinc-50 hover:bg-zinc-700 hover:text-zinc-50",
className,
)}
>
{hasCopied ? (
<CheckIcon className="h-3 w-3" />
) : (
<ClipboardIcon className="h-3 w-3" />
)}
<span className="sr-only">Copy</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => copyCommand("npm")}>npm</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@ -0,0 +1,133 @@
"use client"
import {
type ColumnDef,
flexRender,
getCoreRowModel,
getPaginationRowModel,
type ColumnFiltersState,
getFilteredRowModel,
getSortedRowModel, type SortingState,
useReactTable,
} from "@tanstack/react-table";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import {Button} from "@/components/ui/button";
import {useState} from "react";
import {Input} from "@/components/ui/input";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
fieldToFilter: string
}
export function DataTable<TData, TValue>({
columns,
data,
fieldToFilter,
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = useState<SortingState>([])
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>(
[]
)
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
onColumnFiltersChange: setColumnFilters,
getFilteredRowModel: getFilteredRowModel(),
state: {
sorting,
columnFilters,
},
})
return (
<div className={"w-full"}>
<div className="flex items-center py-4">
<Input
placeholder={`Filter ${fieldToFilter}...`}
value={(table.getColumn(fieldToFilter)?.getFilterValue() as string) ?? ""}
onChange={(event) =>
table.getColumn(fieldToFilter)?.setFilterValue(event.target.value)
}
className="max-w-sm"
/>
</div>
<div className="rounded-md border w-full text-md">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id} className={"font-bold text-lg text-center"}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id} className={"text-center"}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
)
}

View File

@ -0,0 +1,37 @@
"use client";
import { format } from "date-fns";
import { Calendar as CalendarIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import { forwardRef } from "react";
export const DatePicker = forwardRef<
HTMLDivElement,
{
date?: Date;
setDate: (date?: Date) => void;
}
>(function DatePickerCmp({ date, setDate }, ref) {
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant={"outline"}
className={cn(
"w-full justify-start text-left font-normal",
!date && "text-muted-foreground",
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? format(date, "PPP") : <span>Pick a date</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" ref={ref}>
<Calendar mode="single" selected={date} onSelect={setDate} initialFocus />
</PopoverContent>
</Popover>
);
});

View File

@ -1,18 +1,18 @@
"use client" "use client";
import * as React from "react" import * as DialogPrimitive from "@radix-ui/react-dialog";
import * as DialogPrimitive from "@radix-ui/react-dialog" import { X } from "lucide-react";
import { X } from "lucide-react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Dialog = DialogPrimitive.Root const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal const DialogPortal = DialogPrimitive.Portal;
const DialogClose = DialogPrimitive.Close const DialogClose = DialogPrimitive.Close;
const DialogOverlay = React.forwardRef< const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>, React.ElementRef<typeof DialogPrimitive.Overlay>,
@ -22,12 +22,12 @@ const DialogOverlay = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className className,
)} )}
{...props} {...props}
/> />
)) ));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef< const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>, React.ElementRef<typeof DialogPrimitive.Content>,
@ -39,7 +39,7 @@ const DialogContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className className,
)} )}
{...props} {...props}
> >
@ -50,36 +50,27 @@ const DialogContent = React.forwardRef<
</DialogPrimitive.Close> </DialogPrimitive.Close>
</DialogPrimitive.Content> </DialogPrimitive.Content>
</DialogPortal> </DialogPortal>
)) ));
DialogContent.displayName = DialogPrimitive.Content.displayName DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({ const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
className={cn( className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)}
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props} {...props}
/> />
) );
DialogHeader.displayName = "DialogHeader" DialogHeader.displayName = "DialogHeader";
const DialogFooter = ({ const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
className={cn( className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className className,
)} )}
{...props} {...props}
/> />
) );
DialogFooter.displayName = "DialogFooter" DialogFooter.displayName = "DialogFooter";
const DialogTitle = React.forwardRef< const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>, React.ElementRef<typeof DialogPrimitive.Title>,
@ -87,14 +78,11 @@ const DialogTitle = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DialogPrimitive.Title <DialogPrimitive.Title
ref={ref} ref={ref}
className={cn( className={cn("text-lg font-semibold leading-none tracking-tight", className)}
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props} {...props}
/> />
)) ));
DialogTitle.displayName = DialogPrimitive.Title.displayName DialogTitle.displayName = DialogPrimitive.Title.displayName;
const DialogDescription = React.forwardRef< const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>, React.ElementRef<typeof DialogPrimitive.Description>,
@ -105,8 +93,8 @@ const DialogDescription = React.forwardRef<
className={cn("text-sm text-muted-foreground", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)) ));
DialogDescription.displayName = DialogPrimitive.Description.displayName DialogDescription.displayName = DialogPrimitive.Description.displayName;
export { export {
Dialog, Dialog,
@ -119,4 +107,4 @@ export {
DialogFooter, DialogFooter,
DialogTitle, DialogTitle,
DialogDescription, DialogDescription,
} };

View File

@ -1,26 +1,23 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import { Drawer as DrawerPrimitive } from "vaul" import { Drawer as DrawerPrimitive } from "vaul";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Drawer = ({ const Drawer = ({
shouldScaleBackground = true, shouldScaleBackground = true,
...props ...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => ( }: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
<DrawerPrimitive.Root <DrawerPrimitive.Root shouldScaleBackground={shouldScaleBackground} {...props} />
shouldScaleBackground={shouldScaleBackground} );
{...props} Drawer.displayName = "Drawer";
/>
)
Drawer.displayName = "Drawer"
const DrawerTrigger = DrawerPrimitive.Trigger const DrawerTrigger = DrawerPrimitive.Trigger;
const DrawerPortal = DrawerPrimitive.Portal const DrawerPortal = DrawerPrimitive.Portal;
const DrawerClose = DrawerPrimitive.Close const DrawerClose = DrawerPrimitive.Close;
const DrawerOverlay = React.forwardRef< const DrawerOverlay = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Overlay>, React.ElementRef<typeof DrawerPrimitive.Overlay>,
@ -31,8 +28,8 @@ const DrawerOverlay = React.forwardRef<
className={cn("fixed inset-0 z-50 bg-black/80", className)} className={cn("fixed inset-0 z-50 bg-black/80", className)}
{...props} {...props}
/> />
)) ));
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
const DrawerContent = React.forwardRef< const DrawerContent = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Content>, React.ElementRef<typeof DrawerPrimitive.Content>,
@ -44,7 +41,7 @@ const DrawerContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background", "fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
className className,
)} )}
{...props} {...props}
> >
@ -52,30 +49,21 @@ const DrawerContent = React.forwardRef<
{children} {children}
</DrawerPrimitive.Content> </DrawerPrimitive.Content>
</DrawerPortal> </DrawerPortal>
)) ));
DrawerContent.displayName = "DrawerContent" DrawerContent.displayName = "DrawerContent";
const DrawerHeader = ({ const DrawerHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)} className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
{...props} {...props}
/> />
) );
DrawerHeader.displayName = "DrawerHeader" DrawerHeader.displayName = "DrawerHeader";
const DrawerFooter = ({ const DrawerFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
className, <div className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />
...props );
}: React.HTMLAttributes<HTMLDivElement>) => ( DrawerFooter.displayName = "DrawerFooter";
<div
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props}
/>
)
DrawerFooter.displayName = "DrawerFooter"
const DrawerTitle = React.forwardRef< const DrawerTitle = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Title>, React.ElementRef<typeof DrawerPrimitive.Title>,
@ -83,14 +71,11 @@ const DrawerTitle = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DrawerPrimitive.Title <DrawerPrimitive.Title
ref={ref} ref={ref}
className={cn( className={cn("text-lg font-semibold leading-none tracking-tight", className)}
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props} {...props}
/> />
)) ));
DrawerTitle.displayName = DrawerPrimitive.Title.displayName DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
const DrawerDescription = React.forwardRef< const DrawerDescription = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Description>, React.ElementRef<typeof DrawerPrimitive.Description>,
@ -101,8 +86,8 @@ const DrawerDescription = React.forwardRef<
className={cn("text-sm text-muted-foreground", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)) ));
DrawerDescription.displayName = DrawerPrimitive.Description.displayName DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
export { export {
Drawer, Drawer,
@ -115,4 +100,4 @@ export {
DrawerFooter, DrawerFooter,
DrawerTitle, DrawerTitle,
DrawerDescription, DrawerDescription,
} };

View File

@ -1,27 +1,27 @@
"use client" "use client";
import * as React from "react" import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" import { Check, ChevronRight, Circle } from "lucide-react";
import { Check, ChevronRight, Circle } from "lucide-react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const DropdownMenu = DropdownMenuPrimitive.Root const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuGroup = DropdownMenuPrimitive.Group const DropdownMenuGroup = DropdownMenuPrimitive.Group;
const DropdownMenuPortal = DropdownMenuPrimitive.Portal const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
const DropdownMenuSub = DropdownMenuPrimitive.Sub const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuSubTrigger = React.forwardRef< const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>, React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, children, ...props }, ref) => ( >(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger <DropdownMenuPrimitive.SubTrigger
@ -29,16 +29,15 @@ const DropdownMenuSubTrigger = React.forwardRef<
className={cn( className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent", "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
> >
{children} {children}
<ChevronRight className="ml-auto h-4 w-4" /> <ChevronRight className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger> </DropdownMenuPrimitive.SubTrigger>
)) ));
DropdownMenuSubTrigger.displayName = DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef< const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>, React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
@ -48,13 +47,12 @@ const DropdownMenuSubContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className className,
)} )}
{...props} {...props}
/> />
)) ));
DropdownMenuSubContent.displayName = DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef< const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>, React.ElementRef<typeof DropdownMenuPrimitive.Content>,
@ -66,18 +64,18 @@ const DropdownMenuContent = React.forwardRef<
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className className,
)} )}
{...props} {...props}
/> />
</DropdownMenuPrimitive.Portal> </DropdownMenuPrimitive.Portal>
)) ));
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
const DropdownMenuItem = React.forwardRef< const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>, React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item <DropdownMenuPrimitive.Item
@ -85,12 +83,12 @@ const DropdownMenuItem = React.forwardRef<
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
/> />
)) ));
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
const DropdownMenuCheckboxItem = React.forwardRef< const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>, React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
@ -100,7 +98,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className,
)} )}
checked={checked} checked={checked}
{...props} {...props}
@ -112,9 +110,8 @@ const DropdownMenuCheckboxItem = React.forwardRef<
</span> </span>
{children} {children}
</DropdownMenuPrimitive.CheckboxItem> </DropdownMenuPrimitive.CheckboxItem>
)) ));
DropdownMenuCheckboxItem.displayName = DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef< const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>, React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
@ -124,7 +121,7 @@ const DropdownMenuRadioItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className,
)} )}
{...props} {...props}
> >
@ -135,26 +132,22 @@ const DropdownMenuRadioItem = React.forwardRef<
</span> </span>
{children} {children}
</DropdownMenuPrimitive.RadioItem> </DropdownMenuPrimitive.RadioItem>
)) ));
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef< const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>, React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label <DropdownMenuPrimitive.Label
ref={ref} ref={ref}
className={cn( className={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props} {...props}
/> />
)) ));
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
const DropdownMenuSeparator = React.forwardRef< const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>, React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
@ -165,8 +158,8 @@ const DropdownMenuSeparator = React.forwardRef<
className={cn("-mx-1 my-1 h-px bg-muted", className)} className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props} {...props}
/> />
)) ));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({ const DropdownMenuShortcut = ({
className, className,
@ -177,9 +170,9 @@ const DropdownMenuShortcut = ({
className={cn("ml-auto text-xs tracking-widest opacity-60", className)} className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props} {...props}
/> />
) );
} };
DropdownMenuShortcut.displayName = "DropdownMenuShortcut" DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
export { export {
DropdownMenu, DropdownMenu,
@ -197,4 +190,4 @@ export {
DropdownMenuSubContent, DropdownMenuSubContent,
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
DropdownMenuRadioGroup, DropdownMenuRadioGroup,
} };

View File

@ -1,13 +1,13 @@
"use client"; "use client";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { AnimatePresence, motion, LayoutGroup } from "framer-motion";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { AnimatePresence, LayoutGroup, motion } from "framer-motion";
import React, { useCallback, useEffect, useRef, useState } from "react";
export const FlipWords = ({ export const FlipWords = ({
words, words,
duration = 3000, duration = 3000,
className, className,
}: { }: {
words: string[]; words: string[];
duration?: number; duration?: number;
className?: string; className?: string;
@ -60,7 +60,7 @@ export const FlipWords = ({
}} }}
className={cn( className={cn(
"z-10 inline-block relative text-left text-neutral-900 dark:text-neutral-100 px-2", "z-10 inline-block relative text-left text-neutral-900 dark:text-neutral-100 px-2",
className className,
)} )}
key={currentWord} key={currentWord}
> >

View File

@ -1,34 +1,34 @@
import * as React from "react" import type * as LabelPrimitive from "@radix-ui/react-label";
import * as LabelPrimitive from "@radix-ui/react-label" import { Slot } from "@radix-ui/react-slot";
import { Slot } from "@radix-ui/react-slot" import * as React from "react";
import { import {
Controller, Controller,
ControllerProps, type ControllerProps,
FieldPath, type FieldPath,
FieldValues, type FieldValues,
FormProvider, FormProvider,
useFormContext, useFormContext,
} from "react-hook-form" } from "react-hook-form";
import { cn } from "@/lib/utils" import { Label } from "@/components/ui/label";
import { Label } from "@/components/ui/label" import { cn } from "@/lib/utils";
const Form = FormProvider const Form = FormProvider;
type FormFieldContextValue< type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues, TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues> TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = { > = {
name: TName name: TName;
} };
const FormFieldContext = React.createContext<FormFieldContextValue>( const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue {} as FormFieldContextValue,
) );
const FormField = < const FormField = <
TFieldValues extends FieldValues = FieldValues, TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues> TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({ >({
...props ...props
}: ControllerProps<TFieldValues, TName>) => { }: ControllerProps<TFieldValues, TName>) => {
@ -36,21 +36,21 @@ const FormField = <
<FormFieldContext.Provider value={{ name: props.name }}> <FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} /> <Controller {...props} />
</FormFieldContext.Provider> </FormFieldContext.Provider>
) );
} };
const useFormField = () => { const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext) const fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext) const itemContext = React.useContext(FormItemContext);
const { getFieldState, formState } = useFormContext() const { getFieldState, formState } = useFormContext();
const fieldState = getFieldState(fieldContext.name, formState) const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) { if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>") throw new Error("useFormField should be used within <FormField>");
} }
const { id } = itemContext const { id } = itemContext;
return { return {
id, id,
@ -59,36 +59,35 @@ const useFormField = () => {
formDescriptionId: `${id}-form-item-description`, formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`, formMessageId: `${id}-form-item-message`,
...fieldState, ...fieldState,
} };
} };
type FormItemContextValue = { type FormItemContextValue = {
id: string id: string;
} };
const FormItemContext = React.createContext<FormItemContextValue>( const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue {} as FormItemContextValue,
) );
const FormItem = React.forwardRef< const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
HTMLDivElement, ({ className, ...props }, ref) => {
React.HTMLAttributes<HTMLDivElement> const id = React.useId();
>(({ className, ...props }, ref) => {
const id = React.useId()
return ( return (
<FormItemContext.Provider value={{ id }}> <FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} /> <div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider> </FormItemContext.Provider>
) );
}) },
FormItem.displayName = "FormItem" );
FormItem.displayName = "FormItem";
const FormLabel = React.forwardRef< const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>, React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField() const { error, formItemId } = useFormField();
return ( return (
<Label <Label
@ -97,37 +96,35 @@ const FormLabel = React.forwardRef<
htmlFor={formItemId} htmlFor={formItemId}
{...props} {...props}
/> />
) );
}) });
FormLabel.displayName = "FormLabel" FormLabel.displayName = "FormLabel";
const FormControl = React.forwardRef< const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>, React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot> React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => { >(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField() const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
return ( return (
<Slot <Slot
ref={ref} ref={ref}
id={formItemId} id={formItemId}
aria-describedby={ aria-describedby={
!error !error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
} }
aria-invalid={!!error} aria-invalid={!!error}
{...props} {...props}
/> />
) );
}) });
FormControl.displayName = "FormControl" FormControl.displayName = "FormControl";
const FormDescription = React.forwardRef< const FormDescription = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement> React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField() const { formDescriptionId } = useFormField();
return ( return (
<p <p
@ -136,19 +133,19 @@ const FormDescription = React.forwardRef<
className={cn("text-sm text-muted-foreground", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
) );
}) });
FormDescription.displayName = "FormDescription" FormDescription.displayName = "FormDescription";
const FormMessage = React.forwardRef< const FormMessage = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement> React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => { >(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField() const { error, formMessageId } = useFormField();
const body = error ? String(error?.message) : children const body = error ? String(error?.message) : children;
if (!body) { if (!body) {
return null return null;
} }
return ( return (
@ -160,9 +157,9 @@ const FormMessage = React.forwardRef<
> >
{body} {body}
</p> </p>
) );
}) });
FormMessage.displayName = "FormMessage" FormMessage.displayName = "FormMessage";
export { export {
useFormField, useFormField,
@ -173,4 +170,4 @@ export {
FormDescription, FormDescription,
FormMessage, FormMessage,
FormField, FormField,
} };

View File

@ -1,18 +1,18 @@
"use client"; "use client";
import React from "react";
import { import {
type MotionValue,
motion, motion,
useScroll, useScroll,
useTransform,
useSpring, useSpring,
MotionValue, useTransform,
} from "framer-motion"; } from "framer-motion";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import React from "react";
interface HeroParallaxProps { interface HeroParallaxProps {
products: product[]; products: product[];
children?: React.ReactNode children?: React.ReactNode;
} }
interface product { interface product {
@ -21,7 +21,7 @@ interface product {
thumbnail: string; thumbnail: string;
} }
export const HeroParallax = ({ products, children }: HeroParallaxProps ) => { export const HeroParallax = ({ products, children }: HeroParallaxProps) => {
const firstRow = products.slice(0, 5); const firstRow = products.slice(0, 5);
const secondRow = products.slice(5, 10); const secondRow = products.slice(5, 10);
const thirdRow = products.slice(10, 15); const thirdRow = products.slice(10, 15);
@ -35,27 +35,27 @@ export const HeroParallax = ({ products, children }: HeroParallaxProps ) => {
const translateX = useSpring( const translateX = useSpring(
useTransform(scrollYProgress, [0, 1], [0, 1000]), useTransform(scrollYProgress, [0, 1], [0, 1000]),
springConfig springConfig,
); );
const translateXReverse = useSpring( const translateXReverse = useSpring(
useTransform(scrollYProgress, [0, 1], [0, -1000]), useTransform(scrollYProgress, [0, 1], [0, -1000]),
springConfig springConfig,
); );
const rotateX = useSpring( const rotateX = useSpring(
useTransform(scrollYProgress, [0, 0.2], [15, 0]), useTransform(scrollYProgress, [0, 0.2], [15, 0]),
springConfig springConfig,
); );
const opacity = useSpring( const opacity = useSpring(
useTransform(scrollYProgress, [0, 0.2], [0.2, 1]), useTransform(scrollYProgress, [0, 0.2], [0.2, 1]),
springConfig springConfig,
); );
const rotateZ = useSpring( const rotateZ = useSpring(
useTransform(scrollYProgress, [0, 0.2], [20, 0]), useTransform(scrollYProgress, [0, 0.2], [20, 0]),
springConfig springConfig,
); );
const translateY = useSpring( const translateY = useSpring(
useTransform(scrollYProgress, [0, 0.2], [-700, 500]), useTransform(scrollYProgress, [0, 0.2], [-700, 500]),
springConfig springConfig,
); );
return ( return (
<div <div
@ -74,11 +74,7 @@ export const HeroParallax = ({ products, children }: HeroParallaxProps ) => {
> >
<motion.div className="flex flex-row-reverse space-x-reverse space-x-20 mb-20"> <motion.div className="flex flex-row-reverse space-x-reverse space-x-20 mb-20">
{firstRow.map((product) => ( {firstRow.map((product) => (
<ProductCard <ProductCard product={product} translate={translateX} key={product.title} />
product={product}
translate={translateX}
key={product.title}
/>
))} ))}
</motion.div> </motion.div>
<motion.div className="flex flex-row mb-20 space-x-20 "> <motion.div className="flex flex-row mb-20 space-x-20 ">
@ -92,11 +88,7 @@ export const HeroParallax = ({ products, children }: HeroParallaxProps ) => {
</motion.div> </motion.div>
<motion.div className="flex flex-row-reverse space-x-reverse space-x-20"> <motion.div className="flex flex-row-reverse space-x-reverse space-x-20">
{thirdRow.map((product) => ( {thirdRow.map((product) => (
<ProductCard <ProductCard product={product} translate={translateX} key={product.title} />
product={product}
translate={translateX}
key={product.title}
/>
))} ))}
</motion.div> </motion.div>
</motion.div> </motion.div>
@ -107,7 +99,7 @@ export const HeroParallax = ({ products, children }: HeroParallaxProps ) => {
export const ProductCard = ({ export const ProductCard = ({
product, product,
translate, translate,
}: { }: {
product: { product: {
title: string; title: string;
link: string; link: string;
@ -126,10 +118,7 @@ export const ProductCard = ({
key={product.title} key={product.title}
className="group/product h-96 w-[30rem] relative flex-shrink-0" className="group/product h-96 w-[30rem] relative flex-shrink-0"
> >
<Link <Link href={product.link} className="block group-hover/product:shadow-2xl ">
href={product.link}
className="block group-hover/product:shadow-2xl "
>
<Image <Image
src={product.thumbnail} src={product.thumbnail}
height="600" height="600"

View File

@ -1,13 +1,13 @@
"use client" "use client";
import * as React from "react" import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
import * as HoverCardPrimitive from "@radix-ui/react-hover-card" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const HoverCard = HoverCardPrimitive.Root const HoverCard = HoverCardPrimitive.Root;
const HoverCardTrigger = HoverCardPrimitive.Trigger const HoverCardTrigger = HoverCardPrimitive.Trigger;
const HoverCardContent = React.forwardRef< const HoverCardContent = React.forwardRef<
React.ElementRef<typeof HoverCardPrimitive.Content>, React.ElementRef<typeof HoverCardPrimitive.Content>,
@ -19,11 +19,11 @@ const HoverCardContent = React.forwardRef<
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className className,
)} )}
{...props} {...props}
/> />
)) ));
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
export { HoverCard, HoverCardTrigger, HoverCardContent } export { HoverCard, HoverCardTrigger, HoverCardContent };

View File

@ -1,10 +1,10 @@
"use client" "use client";
import * as React from "react" import { OTPInput, OTPInputContext } from "input-otp";
import { OTPInput, OTPInputContext } from "input-otp" import { Dot } from "lucide-react";
import { Dot } from "lucide-react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const InputOTP = React.forwardRef< const InputOTP = React.forwardRef<
React.ElementRef<typeof OTPInput>, React.ElementRef<typeof OTPInput>,
@ -14,28 +14,28 @@ const InputOTP = React.forwardRef<
ref={ref} ref={ref}
containerClassName={cn( containerClassName={cn(
"flex items-center gap-2 has-[:disabled]:opacity-50", "flex items-center gap-2 has-[:disabled]:opacity-50",
containerClassName containerClassName,
)} )}
className={cn("disabled:cursor-not-allowed", className)} className={cn("disabled:cursor-not-allowed", className)}
{...props} {...props}
/> />
)) ));
InputOTP.displayName = "InputOTP" InputOTP.displayName = "InputOTP";
const InputOTPGroup = React.forwardRef< const InputOTPGroup = React.forwardRef<
React.ElementRef<"div">, React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div"> React.ComponentPropsWithoutRef<"div">
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex items-center", className)} {...props} /> <div ref={ref} className={cn("flex items-center", className)} {...props} />
)) ));
InputOTPGroup.displayName = "InputOTPGroup" InputOTPGroup.displayName = "InputOTPGroup";
const InputOTPSlot = React.forwardRef< const InputOTPSlot = React.forwardRef<
React.ElementRef<"div">, React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div"> & { index: number } React.ComponentPropsWithoutRef<"div"> & { index: number }
>(({ index, className, ...props }, ref) => { >(({ index, className, ...props }, ref) => {
const inputOTPContext = React.useContext(OTPInputContext) const inputOTPContext = React.useContext(OTPInputContext);
const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index] const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index];
return ( return (
<div <div
@ -43,7 +43,7 @@ const InputOTPSlot = React.forwardRef<
className={cn( className={cn(
"relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md", "relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
isActive && "z-10 ring-2 ring-ring ring-offset-background", isActive && "z-10 ring-2 ring-ring ring-offset-background",
className className,
)} )}
{...props} {...props}
> >
@ -54,9 +54,9 @@ const InputOTPSlot = React.forwardRef<
</div> </div>
)} )}
</div> </div>
) );
}) });
InputOTPSlot.displayName = "InputOTPSlot" InputOTPSlot.displayName = "InputOTPSlot";
const InputOTPSeparator = React.forwardRef< const InputOTPSeparator = React.forwardRef<
React.ElementRef<"div">, React.ElementRef<"div">,
@ -65,7 +65,7 @@ const InputOTPSeparator = React.forwardRef<
<div ref={ref} role="separator" {...props}> <div ref={ref} role="separator" {...props}>
<Dot /> <Dot />
</div> </div>
)) ));
InputOTPSeparator.displayName = "InputOTPSeparator" InputOTPSeparator.displayName = "InputOTPSeparator";
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator } export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };

View File

@ -1,9 +1,8 @@
import * as React from "react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
export interface InputProps export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>( const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => { ({ className, type, ...props }, ref) => {
@ -11,15 +10,15 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
<input <input
type={type} type={type}
className={cn( className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", "flex h-10 w-full rounded-md border border-input bg-accent px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className className,
)} )}
ref={ref} ref={ref}
{...props} {...props}
/> />
) );
} },
) );
Input.displayName = "Input" Input.displayName = "Input";
export { Input } export { Input };

View File

@ -1,26 +1,22 @@
"use client" "use client";
import * as React from "react" import * as LabelPrimitive from "@radix-ui/react-label";
import * as LabelPrimitive from "@radix-ui/react-label" import { type VariantProps, cva } from "class-variance-authority";
import { cva, type VariantProps } from "class-variance-authority" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const labelVariants = cva( const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
) );
const Label = React.forwardRef< const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>, React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants> VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<LabelPrimitive.Root <LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
ref={ref} ));
className={cn(labelVariants(), className)} Label.displayName = LabelPrimitive.Root.displayName;
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label } export { Label };

View File

@ -1,20 +1,20 @@
"use client" "use client";
import * as React from "react" import * as MenubarPrimitive from "@radix-ui/react-menubar";
import * as MenubarPrimitive from "@radix-ui/react-menubar" import { Check, ChevronRight, Circle } from "lucide-react";
import { Check, ChevronRight, Circle } from "lucide-react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const MenubarMenu = MenubarPrimitive.Menu const MenubarMenu = MenubarPrimitive.Menu;
const MenubarGroup = MenubarPrimitive.Group const MenubarGroup = MenubarPrimitive.Group;
const MenubarPortal = MenubarPrimitive.Portal const MenubarPortal = MenubarPrimitive.Portal;
const MenubarSub = MenubarPrimitive.Sub const MenubarSub = MenubarPrimitive.Sub;
const MenubarRadioGroup = MenubarPrimitive.RadioGroup const MenubarRadioGroup = MenubarPrimitive.RadioGroup;
const Menubar = React.forwardRef< const Menubar = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Root>, React.ElementRef<typeof MenubarPrimitive.Root>,
@ -24,12 +24,12 @@ const Menubar = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-10 items-center space-x-1 rounded-md border bg-background p-1", "flex h-10 items-center space-x-1 rounded-md border bg-background p-1",
className className,
)} )}
{...props} {...props}
/> />
)) ));
Menubar.displayName = MenubarPrimitive.Root.displayName Menubar.displayName = MenubarPrimitive.Root.displayName;
const MenubarTrigger = React.forwardRef< const MenubarTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Trigger>, React.ElementRef<typeof MenubarPrimitive.Trigger>,
@ -39,17 +39,17 @@ const MenubarTrigger = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground", "flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
className className,
)} )}
{...props} {...props}
/> />
)) ));
MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName;
const MenubarSubTrigger = React.forwardRef< const MenubarSubTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubTrigger>, React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & { React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, children, ...props }, ref) => ( >(({ className, inset, children, ...props }, ref) => (
<MenubarPrimitive.SubTrigger <MenubarPrimitive.SubTrigger
@ -57,15 +57,15 @@ const MenubarSubTrigger = React.forwardRef<
className={cn( className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground", "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
> >
{children} {children}
<ChevronRight className="ml-auto h-4 w-4" /> <ChevronRight className="ml-auto h-4 w-4" />
</MenubarPrimitive.SubTrigger> </MenubarPrimitive.SubTrigger>
)) ));
MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName;
const MenubarSubContent = React.forwardRef< const MenubarSubContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubContent>, React.ElementRef<typeof MenubarPrimitive.SubContent>,
@ -75,21 +75,17 @@ const MenubarSubContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className className,
)} )}
{...props} {...props}
/> />
)) ));
MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName;
const MenubarContent = React.forwardRef< const MenubarContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Content>, React.ElementRef<typeof MenubarPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content> React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
>( >(({ className, align = "start", alignOffset = -4, sideOffset = 8, ...props }, ref) => (
(
{ className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
ref
) => (
<MenubarPrimitive.Portal> <MenubarPrimitive.Portal>
<MenubarPrimitive.Content <MenubarPrimitive.Content
ref={ref} ref={ref}
@ -98,19 +94,18 @@ const MenubarContent = React.forwardRef<
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className className,
)} )}
{...props} {...props}
/> />
</MenubarPrimitive.Portal> </MenubarPrimitive.Portal>
) ));
) MenubarContent.displayName = MenubarPrimitive.Content.displayName;
MenubarContent.displayName = MenubarPrimitive.Content.displayName
const MenubarItem = React.forwardRef< const MenubarItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Item>, React.ElementRef<typeof MenubarPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & { React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Item <MenubarPrimitive.Item
@ -118,12 +113,12 @@ const MenubarItem = React.forwardRef<
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
/> />
)) ));
MenubarItem.displayName = MenubarPrimitive.Item.displayName MenubarItem.displayName = MenubarPrimitive.Item.displayName;
const MenubarCheckboxItem = React.forwardRef< const MenubarCheckboxItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.CheckboxItem>, React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
@ -133,7 +128,7 @@ const MenubarCheckboxItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className,
)} )}
checked={checked} checked={checked}
{...props} {...props}
@ -145,8 +140,8 @@ const MenubarCheckboxItem = React.forwardRef<
</span> </span>
{children} {children}
</MenubarPrimitive.CheckboxItem> </MenubarPrimitive.CheckboxItem>
)) ));
MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName;
const MenubarRadioItem = React.forwardRef< const MenubarRadioItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.RadioItem>, React.ElementRef<typeof MenubarPrimitive.RadioItem>,
@ -156,7 +151,7 @@ const MenubarRadioItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className,
)} )}
{...props} {...props}
> >
@ -167,26 +162,22 @@ const MenubarRadioItem = React.forwardRef<
</span> </span>
{children} {children}
</MenubarPrimitive.RadioItem> </MenubarPrimitive.RadioItem>
)) ));
MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName;
const MenubarLabel = React.forwardRef< const MenubarLabel = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Label>, React.ElementRef<typeof MenubarPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & { React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Label <MenubarPrimitive.Label
ref={ref} ref={ref}
className={cn( className={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props} {...props}
/> />
)) ));
MenubarLabel.displayName = MenubarPrimitive.Label.displayName MenubarLabel.displayName = MenubarPrimitive.Label.displayName;
const MenubarSeparator = React.forwardRef< const MenubarSeparator = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Separator>, React.ElementRef<typeof MenubarPrimitive.Separator>,
@ -197,8 +188,8 @@ const MenubarSeparator = React.forwardRef<
className={cn("-mx-1 my-1 h-px bg-muted", className)} className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props} {...props}
/> />
)) ));
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;
const MenubarShortcut = ({ const MenubarShortcut = ({
className, className,
@ -206,15 +197,12 @@ const MenubarShortcut = ({
}: React.HTMLAttributes<HTMLSpanElement>) => { }: React.HTMLAttributes<HTMLSpanElement>) => {
return ( return (
<span <span
className={cn( className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props} {...props}
/> />
) );
} };
MenubarShortcut.displayname = "MenubarShortcut" MenubarShortcut.displayname = "MenubarShortcut";
export { export {
Menubar, Menubar,
@ -233,4 +221,4 @@ export {
MenubarGroup, MenubarGroup,
MenubarSub, MenubarSub,
MenubarShortcut, MenubarShortcut,
} };

View File

@ -1,9 +1,9 @@
import * as React from "react" import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu" import { cva } from "class-variance-authority";
import { cva } from "class-variance-authority" import { ChevronDown } from "lucide-react";
import { ChevronDown } from "lucide-react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const NavigationMenu = React.forwardRef< const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>, React.ElementRef<typeof NavigationMenuPrimitive.Root>,
@ -13,15 +13,15 @@ const NavigationMenu = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative z-10 flex max-w-max flex-1 items-center justify-center", "relative z-10 flex max-w-max flex-1 items-center justify-center",
className className,
)} )}
{...props} {...props}
> >
{children} {children}
<NavigationMenuViewport /> <NavigationMenuViewport />
</NavigationMenuPrimitive.Root> </NavigationMenuPrimitive.Root>
)) ));
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;
const NavigationMenuList = React.forwardRef< const NavigationMenuList = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.List>, React.ElementRef<typeof NavigationMenuPrimitive.List>,
@ -31,18 +31,18 @@ const NavigationMenuList = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"group flex flex-1 list-none items-center justify-center space-x-1", "group flex flex-1 list-none items-center justify-center space-x-1",
className className,
)} )}
{...props} {...props}
/> />
)) ));
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
const NavigationMenuItem = NavigationMenuPrimitive.Item const NavigationMenuItem = NavigationMenuPrimitive.Item;
const navigationMenuTriggerStyle = cva( const navigationMenuTriggerStyle = cva(
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50" "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50",
) );
const NavigationMenuTrigger = React.forwardRef< const NavigationMenuTrigger = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>, React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
@ -59,8 +59,8 @@ const NavigationMenuTrigger = React.forwardRef<
aria-hidden="true" aria-hidden="true"
/> />
</NavigationMenuPrimitive.Trigger> </NavigationMenuPrimitive.Trigger>
)) ));
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;
const NavigationMenuContent = React.forwardRef< const NavigationMenuContent = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>, React.ElementRef<typeof NavigationMenuPrimitive.Content>,
@ -70,14 +70,14 @@ const NavigationMenuContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"md:left-0 md:top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ", "md:left-0 md:top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
className className,
)} )}
{...props} {...props}
/> />
)) ));
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;
const NavigationMenuLink = NavigationMenuPrimitive.Link const NavigationMenuLink = NavigationMenuPrimitive.Link;
const NavigationMenuViewport = React.forwardRef< const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>, React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
@ -87,15 +87,14 @@ const NavigationMenuViewport = React.forwardRef<
<NavigationMenuPrimitive.Viewport <NavigationMenuPrimitive.Viewport
className={cn( className={cn(
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-screen md:w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]", "origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-screen md:w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
className className,
)} )}
ref={ref} ref={ref}
{...props} {...props}
/> />
</div> </div>
)) ));
NavigationMenuViewport.displayName = NavigationMenuViewport.displayName = NavigationMenuPrimitive.Viewport.displayName;
NavigationMenuPrimitive.Viewport.displayName
const NavigationMenuIndicator = React.forwardRef< const NavigationMenuIndicator = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>, React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
@ -105,15 +104,14 @@ const NavigationMenuIndicator = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in", "top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
className className,
)} )}
{...props} {...props}
> >
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" /> <div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator> </NavigationMenuPrimitive.Indicator>
)) ));
NavigationMenuIndicator.displayName = NavigationMenuIndicator.displayName = NavigationMenuPrimitive.Indicator.displayName;
NavigationMenuPrimitive.Indicator.displayName
export { export {
navigationMenuTriggerStyle, navigationMenuTriggerStyle,
@ -125,4 +123,4 @@ export {
NavigationMenuLink, NavigationMenuLink,
NavigationMenuIndicator, NavigationMenuIndicator,
NavigationMenuViewport, NavigationMenuViewport,
} };

View File

@ -1,8 +1,8 @@
import * as React from "react" import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react" import * as React from "react";
import { cn } from "@/lib/utils" import { type ButtonProps, buttonVariants } from "@/components/ui/button";
import { ButtonProps, buttonVariants } from "@/components/ui/button" import { cn } from "@/lib/utils";
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => ( const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
<nav <nav
@ -11,33 +11,31 @@ const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
className={cn("mx-auto flex w-full justify-center", className)} className={cn("mx-auto flex w-full justify-center", className)}
{...props} {...props}
/> />
) );
Pagination.displayName = "Pagination" Pagination.displayName = "Pagination";
const PaginationContent = React.forwardRef< const PaginationContent = React.forwardRef<HTMLUListElement, React.ComponentProps<"ul">>(
HTMLUListElement, ({ className, ...props }, ref) => (
React.ComponentProps<"ul">
>(({ className, ...props }, ref) => (
<ul <ul
ref={ref} ref={ref}
className={cn("flex flex-row items-center gap-1", className)} className={cn("flex flex-row items-center gap-1", className)}
{...props} {...props}
/> />
)) ),
PaginationContent.displayName = "PaginationContent" );
PaginationContent.displayName = "PaginationContent";
const PaginationItem = React.forwardRef< const PaginationItem = React.forwardRef<HTMLLIElement, React.ComponentProps<"li">>(
HTMLLIElement, ({ className, ...props }, ref) => (
React.ComponentProps<"li">
>(({ className, ...props }, ref) => (
<li ref={ref} className={cn("", className)} {...props} /> <li ref={ref} className={cn("", className)} {...props} />
)) ),
PaginationItem.displayName = "PaginationItem" );
PaginationItem.displayName = "PaginationItem";
type PaginationLinkProps = { type PaginationLinkProps = {
isActive?: boolean isActive?: boolean;
} & Pick<ButtonProps, "size"> & } & Pick<ButtonProps, "size"> &
React.ComponentProps<"a"> React.ComponentProps<"a">;
const PaginationLink = ({ const PaginationLink = ({
className, className,
@ -52,12 +50,12 @@ const PaginationLink = ({
variant: isActive ? "outline" : "ghost", variant: isActive ? "outline" : "ghost",
size, size,
}), }),
className className,
)} )}
{...props} {...props}
/> />
) );
PaginationLink.displayName = "PaginationLink" PaginationLink.displayName = "PaginationLink";
const PaginationPrevious = ({ const PaginationPrevious = ({
className, className,
@ -72,8 +70,8 @@ const PaginationPrevious = ({
<ChevronLeft className="h-4 w-4" /> <ChevronLeft className="h-4 w-4" />
<span>Previous</span> <span>Previous</span>
</PaginationLink> </PaginationLink>
) );
PaginationPrevious.displayName = "PaginationPrevious" PaginationPrevious.displayName = "PaginationPrevious";
const PaginationNext = ({ const PaginationNext = ({
className, className,
@ -88,13 +86,10 @@ const PaginationNext = ({
<span>Next</span> <span>Next</span>
<ChevronRight className="h-4 w-4" /> <ChevronRight className="h-4 w-4" />
</PaginationLink> </PaginationLink>
) );
PaginationNext.displayName = "PaginationNext" PaginationNext.displayName = "PaginationNext";
const PaginationEllipsis = ({ const PaginationEllipsis = ({ className, ...props }: React.ComponentProps<"span">) => (
className,
...props
}: React.ComponentProps<"span">) => (
<span <span
aria-hidden aria-hidden
className={cn("flex h-9 w-9 items-center justify-center", className)} className={cn("flex h-9 w-9 items-center justify-center", className)}
@ -103,8 +98,8 @@ const PaginationEllipsis = ({
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More pages</span> <span className="sr-only">More pages</span>
</span> </span>
) );
PaginationEllipsis.displayName = "PaginationEllipsis" PaginationEllipsis.displayName = "PaginationEllipsis";
export { export {
Pagination, Pagination,
@ -114,4 +109,4 @@ export {
PaginationLink, PaginationLink,
PaginationNext, PaginationNext,
PaginationPrevious, PaginationPrevious,
} };

View File

@ -1,13 +1,13 @@
"use client" "use client";
import * as React from "react" import * as PopoverPrimitive from "@radix-ui/react-popover";
import * as PopoverPrimitive from "@radix-ui/react-popover" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Popover = PopoverPrimitive.Root const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverContent = React.forwardRef< const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>, React.ElementRef<typeof PopoverPrimitive.Content>,
@ -20,12 +20,12 @@ const PopoverContent = React.forwardRef<
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className className,
)} )}
{...props} {...props}
/> />
</PopoverPrimitive.Portal> </PopoverPrimitive.Portal>
)) ));
PopoverContent.displayName = PopoverPrimitive.Content.displayName PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent } export { Popover, PopoverTrigger, PopoverContent };

View File

@ -1,9 +1,9 @@
"use client" "use client";
import * as React from "react" import * as ProgressPrimitive from "@radix-ui/react-progress";
import * as ProgressPrimitive from "@radix-ui/react-progress" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Progress = React.forwardRef< const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>, React.ElementRef<typeof ProgressPrimitive.Root>,
@ -13,7 +13,7 @@ const Progress = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative h-4 w-full overflow-hidden rounded-full bg-secondary", "relative h-4 w-full overflow-hidden rounded-full bg-secondary",
className className,
)} )}
{...props} {...props}
> >
@ -22,7 +22,7 @@ const Progress = React.forwardRef<
style={{ transform: `translateX(-${100 - (value || 0)}%)` }} style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/> />
</ProgressPrimitive.Root> </ProgressPrimitive.Root>
)) ));
Progress.displayName = ProgressPrimitive.Root.displayName Progress.displayName = ProgressPrimitive.Root.displayName;
export { Progress } export { Progress };

View File

@ -1,10 +1,10 @@
"use client" "use client";
import * as React from "react" import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" import { Circle } from "lucide-react";
import { Circle } from "lucide-react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const RadioGroup = React.forwardRef< const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>, React.ElementRef<typeof RadioGroupPrimitive.Root>,
@ -16,9 +16,9 @@ const RadioGroup = React.forwardRef<
{...props} {...props}
ref={ref} ref={ref}
/> />
) );
}) });
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
const RadioGroupItem = React.forwardRef< const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>, React.ElementRef<typeof RadioGroupPrimitive.Item>,
@ -29,7 +29,7 @@ const RadioGroupItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", "aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className className,
)} )}
{...props} {...props}
> >
@ -37,8 +37,8 @@ const RadioGroupItem = React.forwardRef<
<Circle className="h-2.5 w-2.5 fill-current text-current" /> <Circle className="h-2.5 w-2.5 fill-current text-current" />
</RadioGroupPrimitive.Indicator> </RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item> </RadioGroupPrimitive.Item>
) );
}) });
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
export { RadioGroup, RadioGroupItem } export { RadioGroup, RadioGroupItem };

View File

@ -1,9 +1,9 @@
"use client" "use client";
import { GripVertical } from "lucide-react" import { GripVertical } from "lucide-react";
import * as ResizablePrimitive from "react-resizable-panels" import * as ResizablePrimitive from "react-resizable-panels";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const ResizablePanelGroup = ({ const ResizablePanelGroup = ({
className, className,
@ -12,25 +12,25 @@ const ResizablePanelGroup = ({
<ResizablePrimitive.PanelGroup <ResizablePrimitive.PanelGroup
className={cn( className={cn(
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col", "flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
className className,
)} )}
{...props} {...props}
/> />
) );
const ResizablePanel = ResizablePrimitive.Panel const ResizablePanel = ResizablePrimitive.Panel;
const ResizableHandle = ({ const ResizableHandle = ({
withHandle, withHandle,
className, className,
...props ...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & { }: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
withHandle?: boolean withHandle?: boolean;
}) => ( }) => (
<ResizablePrimitive.PanelResizeHandle <ResizablePrimitive.PanelResizeHandle
className={cn( className={cn(
"relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90", "relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
className className,
)} )}
{...props} {...props}
> >
@ -40,6 +40,6 @@ const ResizableHandle = ({
</div> </div>
)} )}
</ResizablePrimitive.PanelResizeHandle> </ResizablePrimitive.PanelResizeHandle>
) );
export { ResizablePanelGroup, ResizablePanel, ResizableHandle } export { ResizablePanelGroup, ResizablePanel, ResizableHandle };

View File

@ -1,9 +1,9 @@
"use client" "use client";
import * as React from "react" import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const ScrollArea = React.forwardRef< const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>, React.ElementRef<typeof ScrollAreaPrimitive.Root>,
@ -20,8 +20,8 @@ const ScrollArea = React.forwardRef<
<ScrollBar /> <ScrollBar />
<ScrollAreaPrimitive.Corner /> <ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root> </ScrollAreaPrimitive.Root>
)) ));
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
const ScrollBar = React.forwardRef< const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>, React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
@ -32,17 +32,16 @@ const ScrollBar = React.forwardRef<
orientation={orientation} orientation={orientation}
className={cn( className={cn(
"flex touch-none select-none transition-colors", "flex touch-none select-none transition-colors",
orientation === "vertical" && orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-[1px]",
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" && orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]", "h-2.5 flex-col border-t border-t-transparent p-[1px]",
className className,
)} )}
{...props} {...props}
> >
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" /> <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar> </ScrollAreaPrimitive.ScrollAreaScrollbar>
)) ));
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
export { ScrollArea, ScrollBar } export { ScrollArea, ScrollBar };

View File

@ -1,16 +1,16 @@
"use client" "use client";
import * as React from "react" import * as SelectPrimitive from "@radix-ui/react-select";
import * as SelectPrimitive from "@radix-ui/react-select" import { Check, ChevronDown, ChevronUp } from "lucide-react";
import { Check, ChevronDown, ChevronUp } from "lucide-react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Select = SelectPrimitive.Root const Select = SelectPrimitive.Root;
const SelectGroup = SelectPrimitive.Group const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value const SelectValue = SelectPrimitive.Value;
const SelectTrigger = React.forwardRef< const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>, React.ElementRef<typeof SelectPrimitive.Trigger>,
@ -20,7 +20,7 @@ const SelectTrigger = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className className,
)} )}
{...props} {...props}
> >
@ -29,8 +29,8 @@ const SelectTrigger = React.forwardRef<
<ChevronDown className="h-4 w-4 opacity-50" /> <ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon> </SelectPrimitive.Icon>
</SelectPrimitive.Trigger> </SelectPrimitive.Trigger>
)) ));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectScrollUpButton = React.forwardRef< const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>, React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
@ -38,16 +38,13 @@ const SelectScrollUpButton = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton <SelectPrimitive.ScrollUpButton
ref={ref} ref={ref}
className={cn( className={cn("flex cursor-default items-center justify-center py-1", className)}
"flex cursor-default items-center justify-center py-1",
className
)}
{...props} {...props}
> >
<ChevronUp className="h-4 w-4" /> <ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton> </SelectPrimitive.ScrollUpButton>
)) ));
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
const SelectScrollDownButton = React.forwardRef< const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>, React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
@ -55,17 +52,13 @@ const SelectScrollDownButton = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton <SelectPrimitive.ScrollDownButton
ref={ref} ref={ref}
className={cn( className={cn("flex cursor-default items-center justify-center py-1", className)}
"flex cursor-default items-center justify-center py-1",
className
)}
{...props} {...props}
> >
<ChevronDown className="h-4 w-4" /> <ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton> </SelectPrimitive.ScrollDownButton>
)) ));
SelectScrollDownButton.displayName = SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef< const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>, React.ElementRef<typeof SelectPrimitive.Content>,
@ -78,7 +71,7 @@ const SelectContent = React.forwardRef<
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" && position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className className,
)} )}
position={position} position={position}
{...props} {...props}
@ -88,7 +81,7 @@ const SelectContent = React.forwardRef<
className={cn( className={cn(
"p-1", "p-1",
position === "popper" && position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]" "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
)} )}
> >
{children} {children}
@ -96,8 +89,8 @@ const SelectContent = React.forwardRef<
<SelectScrollDownButton /> <SelectScrollDownButton />
</SelectPrimitive.Content> </SelectPrimitive.Content>
</SelectPrimitive.Portal> </SelectPrimitive.Portal>
)) ));
SelectContent.displayName = SelectPrimitive.Content.displayName SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef< const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>, React.ElementRef<typeof SelectPrimitive.Label>,
@ -108,8 +101,8 @@ const SelectLabel = React.forwardRef<
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)} className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
{...props} {...props}
/> />
)) ));
SelectLabel.displayName = SelectPrimitive.Label.displayName SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = React.forwardRef< const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>, React.ElementRef<typeof SelectPrimitive.Item>,
@ -119,7 +112,7 @@ const SelectItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className,
)} )}
{...props} {...props}
> >
@ -131,8 +124,8 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item> </SelectPrimitive.Item>
)) ));
SelectItem.displayName = SelectPrimitive.Item.displayName SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef< const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>, React.ElementRef<typeof SelectPrimitive.Separator>,
@ -143,8 +136,8 @@ const SelectSeparator = React.forwardRef<
className={cn("-mx-1 my-1 h-px bg-muted", className)} className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props} {...props}
/> />
)) ));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export { export {
Select, Select,
@ -157,4 +150,4 @@ export {
SelectSeparator, SelectSeparator,
SelectScrollUpButton, SelectScrollUpButton,
SelectScrollDownButton, SelectScrollDownButton,
} };

View File

@ -1,18 +1,14 @@
"use client" "use client";
import * as React from "react" import * as SeparatorPrimitive from "@radix-ui/react-separator";
import * as SeparatorPrimitive from "@radix-ui/react-separator" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Separator = React.forwardRef< const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>, React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root> React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>( >(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
(
{ className, orientation = "horizontal", decorative = true, ...props },
ref
) => (
<SeparatorPrimitive.Root <SeparatorPrimitive.Root
ref={ref} ref={ref}
decorative={decorative} decorative={decorative}
@ -20,12 +16,11 @@ const Separator = React.forwardRef<
className={cn( className={cn(
"shrink-0 bg-border", "shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]", orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className className,
)} )}
{...props} {...props}
/> />
) ));
) Separator.displayName = SeparatorPrimitive.Root.displayName;
Separator.displayName = SeparatorPrimitive.Root.displayName
export { Separator } export { Separator };

View File

@ -1,19 +1,19 @@
"use client" "use client";
import * as React from "react" import * as SheetPrimitive from "@radix-ui/react-dialog";
import * as SheetPrimitive from "@radix-ui/react-dialog" import { type VariantProps, cva } from "class-variance-authority";
import { cva, type VariantProps } from "class-variance-authority" import { X } from "lucide-react";
import { X } from "lucide-react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Sheet = SheetPrimitive.Root const Sheet = SheetPrimitive.Root;
const SheetTrigger = SheetPrimitive.Trigger const SheetTrigger = SheetPrimitive.Trigger;
const SheetClose = SheetPrimitive.Close const SheetClose = SheetPrimitive.Close;
const SheetPortal = SheetPrimitive.Portal const SheetPortal = SheetPrimitive.Portal;
const SheetOverlay = React.forwardRef< const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>, React.ElementRef<typeof SheetPrimitive.Overlay>,
@ -22,13 +22,13 @@ const SheetOverlay = React.forwardRef<
<SheetPrimitive.Overlay <SheetPrimitive.Overlay
className={cn( className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className className,
)} )}
{...props} {...props}
ref={ref} ref={ref}
/> />
)) ));
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva( const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
@ -46,8 +46,8 @@ const sheetVariants = cva(
defaultVariants: { defaultVariants: {
side: "right", side: "right",
}, },
} },
) );
interface SheetContentProps interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>, extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
@ -71,36 +71,27 @@ const SheetContent = React.forwardRef<
</SheetPrimitive.Close> </SheetPrimitive.Close>
</SheetPrimitive.Content> </SheetPrimitive.Content>
</SheetPortal> </SheetPortal>
)) ));
SheetContent.displayName = SheetPrimitive.Content.displayName SheetContent.displayName = SheetPrimitive.Content.displayName;
const SheetHeader = ({ const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
className={cn( className={cn("flex flex-col space-y-2 text-center sm:text-left", className)}
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props} {...props}
/> />
) );
SheetHeader.displayName = "SheetHeader" SheetHeader.displayName = "SheetHeader";
const SheetFooter = ({ const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
className={cn( className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className className,
)} )}
{...props} {...props}
/> />
) );
SheetFooter.displayName = "SheetFooter" SheetFooter.displayName = "SheetFooter";
const SheetTitle = React.forwardRef< const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>, React.ElementRef<typeof SheetPrimitive.Title>,
@ -111,8 +102,8 @@ const SheetTitle = React.forwardRef<
className={cn("text-lg font-semibold text-foreground", className)} className={cn("text-lg font-semibold text-foreground", className)}
{...props} {...props}
/> />
)) ));
SheetTitle.displayName = SheetPrimitive.Title.displayName SheetTitle.displayName = SheetPrimitive.Title.displayName;
const SheetDescription = React.forwardRef< const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>, React.ElementRef<typeof SheetPrimitive.Description>,
@ -123,8 +114,8 @@ const SheetDescription = React.forwardRef<
className={cn("text-sm text-muted-foreground", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)) ));
SheetDescription.displayName = SheetPrimitive.Description.displayName SheetDescription.displayName = SheetPrimitive.Description.displayName;
export { export {
Sheet, Sheet,
@ -137,4 +128,4 @@ export {
SheetFooter, SheetFooter,
SheetTitle, SheetTitle,
SheetDescription, SheetDescription,
} };

View File

@ -1,15 +1,9 @@
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
function Skeleton({ function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return ( return (
<div <div className={cn("animate-pulse rounded-md bg-muted", className)} {...props} />
className={cn("animate-pulse rounded-md bg-muted", className)} );
{...props}
/>
)
} }
export { Skeleton } export { Skeleton };

View File

@ -1,9 +1,9 @@
"use client" "use client";
import * as React from "react" import * as SliderPrimitive from "@radix-ui/react-slider";
import * as SliderPrimitive from "@radix-ui/react-slider" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Slider = React.forwardRef< const Slider = React.forwardRef<
React.ElementRef<typeof SliderPrimitive.Root>, React.ElementRef<typeof SliderPrimitive.Root>,
@ -11,10 +11,7 @@ const Slider = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SliderPrimitive.Root <SliderPrimitive.Root
ref={ref} ref={ref}
className={cn( className={cn("relative flex w-full touch-none select-none items-center", className)}
"relative flex w-full touch-none select-none items-center",
className
)}
{...props} {...props}
> >
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary"> <SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
@ -22,7 +19,7 @@ const Slider = React.forwardRef<
</SliderPrimitive.Track> </SliderPrimitive.Track>
<SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" /> <SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
</SliderPrimitive.Root> </SliderPrimitive.Root>
)) ));
Slider.displayName = SliderPrimitive.Root.displayName Slider.displayName = SliderPrimitive.Root.displayName;
export { Slider } export { Slider };

View File

@ -1,12 +1,12 @@
"use client" "use client";
import { useTheme } from "next-themes" import { useTheme } from "next-themes";
import { Toaster as Sonner } from "sonner" import { Toaster as Sonner } from "sonner";
type ToasterProps = React.ComponentProps<typeof Sonner> type ToasterProps = React.ComponentProps<typeof Sonner>;
const Toaster = ({ ...props }: ToasterProps) => { const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme() const { theme = "system" } = useTheme();
return ( return (
<Sonner <Sonner
@ -19,13 +19,12 @@ const Toaster = ({ ...props }: ToasterProps) => {
description: "group-[.toast]:text-muted-foreground", description: "group-[.toast]:text-muted-foreground",
actionButton: actionButton:
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground", "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
cancelButton: cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
}, },
}} }}
{...props} {...props}
/> />
) );
} };
export { Toaster } export { Toaster };

View File

@ -1,9 +1,9 @@
"use client" "use client";
import * as React from "react" import * as SwitchPrimitives from "@radix-ui/react-switch";
import * as SwitchPrimitives from "@radix-ui/react-switch" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Switch = React.forwardRef< const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>, React.ElementRef<typeof SwitchPrimitives.Root>,
@ -12,18 +12,18 @@ const Switch = React.forwardRef<
<SwitchPrimitives.Root <SwitchPrimitives.Root
className={cn( className={cn(
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input", "peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className className,
)} )}
{...props} {...props}
ref={ref} ref={ref}
> >
<SwitchPrimitives.Thumb <SwitchPrimitives.Thumb
className={cn( className={cn(
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0" "pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0",
)} )}
/> />
</SwitchPrimitives.Root> </SwitchPrimitives.Root>
)) ));
Switch.displayName = SwitchPrimitives.Root.displayName Switch.displayName = SwitchPrimitives.Root.displayName;
export { Switch } export { Switch };

View File

@ -1,11 +1,9 @@
import * as React from "react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Table = React.forwardRef< const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
HTMLTableElement, ({ className, ...props }, ref) => (
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto"> <div className="relative w-full overflow-auto">
<table <table
ref={ref} ref={ref}
@ -13,28 +11,25 @@ const Table = React.forwardRef<
{...props} {...props}
/> />
</div> </div>
)) ),
Table.displayName = "Table" );
Table.displayName = "Table";
const TableHeader = React.forwardRef< const TableHeader = React.forwardRef<
HTMLTableSectionElement, HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement> React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} /> <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
)) ));
TableHeader.displayName = "TableHeader" TableHeader.displayName = "TableHeader";
const TableBody = React.forwardRef< const TableBody = React.forwardRef<
HTMLTableSectionElement, HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement> React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<tbody <tbody ref={ref} className={cn("[&_tr:last-child]:border-0", className)} {...props} />
ref={ref} ));
className={cn("[&_tr:last-child]:border-0", className)} TableBody.displayName = "TableBody";
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef< const TableFooter = React.forwardRef<
HTMLTableSectionElement, HTMLTableSectionElement,
@ -42,14 +37,11 @@ const TableFooter = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<tfoot <tfoot
ref={ref} ref={ref}
className={cn( className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className
)}
{...props} {...props}
/> />
)) ));
TableFooter.displayName = "TableFooter" TableFooter.displayName = "TableFooter";
const TableRow = React.forwardRef< const TableRow = React.forwardRef<
HTMLTableRowElement, HTMLTableRowElement,
@ -59,12 +51,12 @@ const TableRow = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className className,
)} )}
{...props} {...props}
/> />
)) ));
TableRow.displayName = "TableRow" TableRow.displayName = "TableRow";
const TableHead = React.forwardRef< const TableHead = React.forwardRef<
HTMLTableCellElement, HTMLTableCellElement,
@ -74,12 +66,12 @@ const TableHead = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0", "h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
className className,
)} )}
{...props} {...props}
/> />
)) ));
TableHead.displayName = "TableHead" TableHead.displayName = "TableHead";
const TableCell = React.forwardRef< const TableCell = React.forwardRef<
HTMLTableCellElement, HTMLTableCellElement,
@ -90,8 +82,8 @@ const TableCell = React.forwardRef<
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)} className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
{...props} {...props}
/> />
)) ));
TableCell.displayName = "TableCell" TableCell.displayName = "TableCell";
const TableCaption = React.forwardRef< const TableCaption = React.forwardRef<
HTMLTableCaptionElement, HTMLTableCaptionElement,
@ -102,8 +94,8 @@ const TableCaption = React.forwardRef<
className={cn("mt-4 text-sm text-muted-foreground", className)} className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)) ));
TableCaption.displayName = "TableCaption" TableCaption.displayName = "TableCaption";
export { export {
Table, Table,
@ -114,4 +106,4 @@ export {
TableRow, TableRow,
TableCell, TableCell,
TableCaption, TableCaption,
} };

View File

@ -1,11 +1,11 @@
"use client" "use client";
import * as React from "react" import * as TabsPrimitive from "@radix-ui/react-tabs";
import * as TabsPrimitive from "@radix-ui/react-tabs" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Tabs = TabsPrimitive.Root const Tabs = TabsPrimitive.Root;
const TabsList = React.forwardRef< const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>, React.ElementRef<typeof TabsPrimitive.List>,
@ -15,12 +15,12 @@ const TabsList = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground", "inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
className className,
)} )}
{...props} {...props}
/> />
)) ));
TabsList.displayName = TabsPrimitive.List.displayName TabsList.displayName = TabsPrimitive.List.displayName;
const TabsTrigger = React.forwardRef< const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>, React.ElementRef<typeof TabsPrimitive.Trigger>,
@ -30,12 +30,12 @@ const TabsTrigger = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm", "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
className className,
)} )}
{...props} {...props}
/> />
)) ));
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
const TabsContent = React.forwardRef< const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>, React.ElementRef<typeof TabsPrimitive.Content>,
@ -45,11 +45,11 @@ const TabsContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className className,
)} )}
{...props} {...props}
/> />
)) ));
TabsContent.displayName = TabsPrimitive.Content.displayName TabsContent.displayName = TabsPrimitive.Content.displayName;
export { Tabs, TabsList, TabsTrigger, TabsContent } export { Tabs, TabsList, TabsTrigger, TabsContent };

View File

@ -1,6 +1,6 @@
import * as React from "react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
export interface TextareaProps export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {} extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
@ -11,14 +11,14 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
<textarea <textarea
className={cn( className={cn(
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className className,
)} )}
ref={ref} ref={ref}
{...props} {...props}
/> />
) );
} },
) );
Textarea.displayName = "Textarea" Textarea.displayName = "Textarea";
export { Textarea } export { Textarea };

View File

@ -1,63 +1,77 @@
import {Bug, CircleAlert, CircleCheckBig, CircleHelp, MessageSquareText, Minus, OctagonX, Plus} from "lucide-react"; import {
Bug,
CircleAlert,
CircleCheckBig,
CircleHelp,
MessageSquareText,
Minus,
OctagonX,
Plus,
} from "lucide-react";
import React from "react"; import React from "react";
export enum toastType { export enum toastType {
info = "info", info = "info",
warn = "warn", warn = "warn",
error = "error", error = "error",
refused= "refused", refused = "refused",
success = "success", success = "success",
add = "add", add = "add",
del = "del", del = "del",
message = "message" message = "message",
} }
export function ToastBox({title, message, type}: {title?: string, message: string, type: toastType}) { export function ToastBox({
title,
message,
type,
}: { title?: string; message: string; type: toastType }) {
let icon: any; let icon: any;
let bgColor: string; let bgColor: string;
switch (type) { switch (type) {
case toastType.message : case toastType.message:
icon = <MessageSquareText /> icon = <MessageSquareText />;
bgColor = 'bg-accent' bgColor = "bg-accent";
break break;
case toastType.add: case toastType.add:
icon = <Plus /> icon = <Plus />;
bgColor = 'bg-accent' bgColor = "bg-accent";
break break;
case toastType.del: case toastType.del:
icon = <Minus /> icon = <Minus />;
bgColor = 'bg-accent' bgColor = "bg-accent";
break break;
case toastType.info: case toastType.info:
icon = <CircleHelp /> icon = <CircleHelp />;
bgColor = 'bg-accent' bgColor = "bg-accent";
break break;
case toastType.warn: case toastType.warn:
icon = <CircleAlert /> icon = <CircleAlert />;
bgColor = 'bg-orange-500' bgColor = "bg-orange-500";
break break;
case toastType.error: case toastType.error:
icon = <Bug /> icon = <Bug />;
bgColor = 'bg-red-500' bgColor = "bg-red-500";
break break;
case toastType.success: case toastType.success:
icon = <CircleCheckBig /> icon = <CircleCheckBig />;
bgColor = 'bg-green-500' bgColor = "bg-green-500";
break break;
case toastType.refused: case toastType.refused:
icon = <OctagonX /> icon = <OctagonX />;
bgColor = 'bg-red-700' bgColor = "bg-red-700";
break break;
} }
return ( return (
<div className={`flex flex-row items-center gap-2 scale-90 md:scale-100 p-2 rounded border ${bgColor}`}> <div
className={`flex flex-row items-center gap-2 scale-90 md:scale-100 p-2 rounded border ${bgColor}`}
>
{icon} {icon}
<div className={"flex flex-col justify-center items-start"}> <div className={"flex flex-col justify-center items-start"}>
{title && <h3 className={"text-nowrap font-bold text-xl"}>{title}</h3>} {title && <h3 className={"text-nowrap font-bold text-xl"}>{title}</h3>}
<p className={"text-wrap"}>{message}</p> <p className={"text-wrap"}>{message}</p>
</div> </div>
</div> </div>
) );
} }

View File

@ -1,13 +1,13 @@
"use client" "use client";
import * as React from "react" import * as ToastPrimitives from "@radix-ui/react-toast";
import * as ToastPrimitives from "@radix-ui/react-toast" import { type VariantProps, cva } from "class-variance-authority";
import { cva, type VariantProps } from "class-variance-authority" import { X } from "lucide-react";
import { X } from "lucide-react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const ToastProvider = ToastPrimitives.Provider const ToastProvider = ToastPrimitives.Provider;
const ToastViewport = React.forwardRef< const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>, React.ElementRef<typeof ToastPrimitives.Viewport>,
@ -17,12 +17,12 @@ const ToastViewport = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]", "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
className className,
)} )}
{...props} {...props}
/> />
)) ));
ToastViewport.displayName = ToastPrimitives.Viewport.displayName ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
const toastVariants = cva( const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
@ -37,8 +37,8 @@ const toastVariants = cva(
defaultVariants: { defaultVariants: {
variant: "default", variant: "default",
}, },
} },
) );
const Toast = React.forwardRef< const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>, React.ElementRef<typeof ToastPrimitives.Root>,
@ -51,9 +51,9 @@ const Toast = React.forwardRef<
className={cn(toastVariants({ variant }), className)} className={cn(toastVariants({ variant }), className)}
{...props} {...props}
/> />
) );
}) });
Toast.displayName = ToastPrimitives.Root.displayName Toast.displayName = ToastPrimitives.Root.displayName;
const ToastAction = React.forwardRef< const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>, React.ElementRef<typeof ToastPrimitives.Action>,
@ -63,12 +63,12 @@ const ToastAction = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive", "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
className className,
)} )}
{...props} {...props}
/> />
)) ));
ToastAction.displayName = ToastPrimitives.Action.displayName ToastAction.displayName = ToastPrimitives.Action.displayName;
const ToastClose = React.forwardRef< const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>, React.ElementRef<typeof ToastPrimitives.Close>,
@ -78,15 +78,15 @@ const ToastClose = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600", "absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
className className,
)} )}
toast-close="" toast-close=""
{...props} {...props}
> >
<X className="h-4 w-4" /> <X className="h-4 w-4" />
</ToastPrimitives.Close> </ToastPrimitives.Close>
)) ));
ToastClose.displayName = ToastPrimitives.Close.displayName ToastClose.displayName = ToastPrimitives.Close.displayName;
const ToastTitle = React.forwardRef< const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>, React.ElementRef<typeof ToastPrimitives.Title>,
@ -97,8 +97,8 @@ const ToastTitle = React.forwardRef<
className={cn("text-sm font-semibold", className)} className={cn("text-sm font-semibold", className)}
{...props} {...props}
/> />
)) ));
ToastTitle.displayName = ToastPrimitives.Title.displayName ToastTitle.displayName = ToastPrimitives.Title.displayName;
const ToastDescription = React.forwardRef< const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>, React.ElementRef<typeof ToastPrimitives.Description>,
@ -109,12 +109,12 @@ const ToastDescription = React.forwardRef<
className={cn("text-sm opacity-90", className)} className={cn("text-sm opacity-90", className)}
{...props} {...props}
/> />
)) ));
ToastDescription.displayName = ToastPrimitives.Description.displayName ToastDescription.displayName = ToastPrimitives.Description.displayName;
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast> type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
type ToastActionElement = React.ReactElement<typeof ToastAction> type ToastActionElement = React.ReactElement<typeof ToastAction>;
export { export {
type ToastProps, type ToastProps,
@ -126,4 +126,4 @@ export {
ToastDescription, ToastDescription,
ToastClose, ToastClose,
ToastAction, ToastAction,
} };

View File

@ -1,4 +1,4 @@
"use client" "use client";
import { import {
Toast, Toast,
@ -7,29 +7,25 @@ import {
ToastProvider, ToastProvider,
ToastTitle, ToastTitle,
ToastViewport, ToastViewport,
} from "@/components/ui/toast" } from "@/components/ui/toast";
import { useToast } from "@/components/ui/use-toast" import { useToast } from "@/components/ui/use-toast";
export function Toaster() { export function Toaster() {
const { toasts } = useToast() const { toasts } = useToast();
return ( return (
<ToastProvider> <ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }) { {toasts.map(({ id, title, description, action, ...props }) => (
return (
<Toast key={id} {...props}> <Toast key={id} {...props}>
<div className="grid gap-1"> <div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>} {title && <ToastTitle>{title}</ToastTitle>}
{description && ( {description && <ToastDescription>{description}</ToastDescription>}
<ToastDescription>{description}</ToastDescription>
)}
</div> </div>
{action} {action}
<ToastClose /> <ToastClose />
</Toast> </Toast>
) ))}
})}
<ToastViewport /> <ToastViewport />
</ToastProvider> </ToastProvider>
) );
} }

View File

@ -1,18 +1,16 @@
"use client" "use client";
import * as React from "react" import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group" import type { VariantProps } from "class-variance-authority";
import { VariantProps } from "class-variance-authority" import * as React from "react";
import { cn } from "@/lib/utils" import { toggleVariants } from "@/components/ui/toggle";
import { toggleVariants } from "@/components/ui/toggle" import { cn } from "@/lib/utils";
const ToggleGroupContext = React.createContext< const ToggleGroupContext = React.createContext<VariantProps<typeof toggleVariants>>({
VariantProps<typeof toggleVariants>
>({
size: "default", size: "default",
variant: "default", variant: "default",
}) });
const ToggleGroup = React.forwardRef< const ToggleGroup = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Root>, React.ElementRef<typeof ToggleGroupPrimitive.Root>,
@ -28,16 +26,16 @@ const ToggleGroup = React.forwardRef<
{children} {children}
</ToggleGroupContext.Provider> </ToggleGroupContext.Provider>
</ToggleGroupPrimitive.Root> </ToggleGroupPrimitive.Root>
)) ));
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
const ToggleGroupItem = React.forwardRef< const ToggleGroupItem = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Item>, React.ElementRef<typeof ToggleGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> & React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
VariantProps<typeof toggleVariants> VariantProps<typeof toggleVariants>
>(({ className, children, variant, size, ...props }, ref) => { >(({ className, children, variant, size, ...props }, ref) => {
const context = React.useContext(ToggleGroupContext) const context = React.useContext(ToggleGroupContext);
return ( return (
<ToggleGroupPrimitive.Item <ToggleGroupPrimitive.Item
@ -47,15 +45,15 @@ const ToggleGroupItem = React.forwardRef<
variant: context.variant || variant, variant: context.variant || variant,
size: context.size || size, size: context.size || size,
}), }),
className className,
)} )}
{...props} {...props}
> >
{children} {children}
</ToggleGroupPrimitive.Item> </ToggleGroupPrimitive.Item>
) );
}) });
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
export { ToggleGroup, ToggleGroupItem } export { ToggleGroup, ToggleGroupItem };

View File

@ -1,10 +1,10 @@
"use client" "use client";
import * as React from "react" import * as TogglePrimitive from "@radix-ui/react-toggle";
import * as TogglePrimitive from "@radix-ui/react-toggle" import { type VariantProps, cva } from "class-variance-authority";
import { cva, type VariantProps } from "class-variance-authority" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const toggleVariants = cva( const toggleVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground", "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
@ -25,8 +25,8 @@ const toggleVariants = cva(
variant: "default", variant: "default",
size: "default", size: "default",
}, },
} },
) );
const Toggle = React.forwardRef< const Toggle = React.forwardRef<
React.ElementRef<typeof TogglePrimitive.Root>, React.ElementRef<typeof TogglePrimitive.Root>,
@ -38,8 +38,8 @@ const Toggle = React.forwardRef<
className={cn(toggleVariants({ variant, size, className }))} className={cn(toggleVariants({ variant, size, className }))}
{...props} {...props}
/> />
)) ));
Toggle.displayName = TogglePrimitive.Root.displayName Toggle.displayName = TogglePrimitive.Root.displayName;
export { Toggle, toggleVariants } export { Toggle, toggleVariants };

View File

@ -1,15 +1,15 @@
"use client" "use client";
import * as React from "react" import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import * as TooltipPrimitive from "@radix-ui/react-tooltip" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const TooltipProvider = TooltipPrimitive.Provider const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef< const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>, React.ElementRef<typeof TooltipPrimitive.Content>,
@ -20,11 +20,11 @@ const TooltipContent = React.forwardRef<
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className className,
)} )}
{...props} {...props}
/> />
)) ));
TooltipContent.displayName = TooltipPrimitive.Content.displayName TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };

View File

@ -1,78 +1,75 @@
"use client" "use client";
// Inspired by react-hot-toast library // Inspired by react-hot-toast library
import * as React from "react" import * as React from "react";
import type { import type { ToastActionElement, ToastProps } from "@/components/ui/toast";
ToastActionElement,
ToastProps,
} from "@/components/ui/toast"
const TOAST_LIMIT = 1 const TOAST_LIMIT = 1;
const TOAST_REMOVE_DELAY = 1000000 const TOAST_REMOVE_DELAY = 1000000;
type ToasterToast = ToastProps & { type ToasterToast = ToastProps & {
id: string id: string;
title?: React.ReactNode title?: React.ReactNode;
description?: React.ReactNode description?: React.ReactNode;
action?: ToastActionElement action?: ToastActionElement;
} };
const actionTypes = { const actionTypes = {
ADD_TOAST: "ADD_TOAST", ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST", UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST", DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST", REMOVE_TOAST: "REMOVE_TOAST",
} as const } as const;
let count = 0 let count = 0;
function genId() { function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER count = (count + 1) % Number.MAX_SAFE_INTEGER;
return count.toString() return count.toString();
} }
type ActionType = typeof actionTypes type ActionType = typeof actionTypes;
type Action = type Action =
| { | {
type: ActionType["ADD_TOAST"] type: ActionType["ADD_TOAST"];
toast: ToasterToast toast: ToasterToast;
} }
| { | {
type: ActionType["UPDATE_TOAST"] type: ActionType["UPDATE_TOAST"];
toast: Partial<ToasterToast> toast: Partial<ToasterToast>;
} }
| { | {
type: ActionType["DISMISS_TOAST"] type: ActionType["DISMISS_TOAST"];
toastId?: ToasterToast["id"] toastId?: ToasterToast["id"];
} }
| { | {
type: ActionType["REMOVE_TOAST"] type: ActionType["REMOVE_TOAST"];
toastId?: ToasterToast["id"] toastId?: ToasterToast["id"];
} };
interface State { interface State {
toasts: ToasterToast[] toasts: ToasterToast[];
} }
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>() const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
const addToRemoveQueue = (toastId: string) => { const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) { if (toastTimeouts.has(toastId)) {
return return;
} }
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
toastTimeouts.delete(toastId) toastTimeouts.delete(toastId);
dispatch({ dispatch({
type: "REMOVE_TOAST", type: "REMOVE_TOAST",
toastId: toastId, toastId: toastId,
}) });
}, TOAST_REMOVE_DELAY) }, TOAST_REMOVE_DELAY);
toastTimeouts.set(toastId, timeout) toastTimeouts.set(toastId, timeout);
} };
export const reducer = (state: State, action: Action): State => { export const reducer = (state: State, action: Action): State => {
switch (action.type) { switch (action.type) {
@ -80,27 +77,27 @@ export const reducer = (state: State, action: Action): State => {
return { return {
...state, ...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
} };
case "UPDATE_TOAST": case "UPDATE_TOAST":
return { return {
...state, ...state,
toasts: state.toasts.map((t) => toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t t.id === action.toast.id ? { ...t, ...action.toast } : t,
), ),
} };
case "DISMISS_TOAST": { case "DISMISS_TOAST": {
const { toastId } = action const { toastId } = action;
// ! Side effects ! - This could be extracted into a dismissToast() action, // ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity // but I'll keep it here for simplicity
if (toastId) { if (toastId) {
addToRemoveQueue(toastId) addToRemoveQueue(toastId);
} else { } else {
state.toasts.forEach((toast) => { state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id) addToRemoveQueue(toast.id);
}) });
} }
return { return {
@ -111,46 +108,46 @@ export const reducer = (state: State, action: Action): State => {
...t, ...t,
open: false, open: false,
} }
: t : t,
), ),
} };
} }
case "REMOVE_TOAST": case "REMOVE_TOAST":
if (action.toastId === undefined) { if (action.toastId === undefined) {
return { return {
...state, ...state,
toasts: [], toasts: [],
} };
} }
return { return {
...state, ...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId), toasts: state.toasts.filter((t) => t.id !== action.toastId),
};
} }
} };
}
const listeners: Array<(state: State) => void> = [] const listeners: Array<(state: State) => void> = [];
let memoryState: State = { toasts: [] } let memoryState: State = { toasts: [] };
function dispatch(action: Action) { function dispatch(action: Action) {
memoryState = reducer(memoryState, action) memoryState = reducer(memoryState, action);
listeners.forEach((listener) => { listeners.forEach((listener) => {
listener(memoryState) listener(memoryState);
}) });
} }
type Toast = Omit<ToasterToast, "id"> type Toast = Omit<ToasterToast, "id">;
function toast({ ...props }: Toast) { function toast({ ...props }: Toast) {
const id = genId() const id = genId();
const update = (props: ToasterToast) => const update = (props: ToasterToast) =>
dispatch({ dispatch({
type: "UPDATE_TOAST", type: "UPDATE_TOAST",
toast: { ...props, id }, toast: { ...props, id },
}) });
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
dispatch({ dispatch({
type: "ADD_TOAST", type: "ADD_TOAST",
@ -159,36 +156,36 @@ function toast({ ...props }: Toast) {
id, id,
open: true, open: true,
onOpenChange: (open) => { onOpenChange: (open) => {
if (!open) dismiss() if (!open) dismiss();
}, },
}, },
}) });
return { return {
id: id, id: id,
dismiss, dismiss,
update, update,
} };
} }
function useToast() { function useToast() {
const [state, setState] = React.useState<State>(memoryState) const [state, setState] = React.useState<State>(memoryState);
React.useEffect(() => { React.useEffect(() => {
listeners.push(setState) listeners.push(setState);
return () => { return () => {
const index = listeners.indexOf(setState) const index = listeners.indexOf(setState);
if (index > -1) { if (index > -1) {
listeners.splice(index, 1) listeners.splice(index, 1);
} }
} };
}, [state]) }, [state]);
return { return {
...state, ...state,
toast, toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
} };
} }
export { useToast, toast } export { useToast, toast };

View File

@ -0,0 +1,90 @@
import {type ICryptoInWalletInfo, ITrade, type IUserWalletCryptos} from "@/interfaces/crypto.interface";
import type { IUserData } from "@/interfaces/userdata.interface";
// ----- Request -----
export interface IApiRegisterReq {
firstName: string;
lastName: string;
pseudo: string;
city: string;
email: string;
password: string;
age: number;
}
export interface IApiLoginReq {
email: string;
password: string;
}
export interface IApiTradeCreateRq {
id_offer: string;
}
export interface IApiOfferCreateReq {
id_crypto: string;
amount: number;
}
export interface IApiCreateReferralCodeReq {
name: string;
value: number;
}
export interface IApiDoTradeReq {
id_offer: string;
}
export interface IApiDoOfferReq {
id_crypto: string;
amount: number;
}
// ----- Response -----
export interface IAbstractApiResponse {
message?: Array<string> | string;
error?: string;
statusCode?: number;
}
export interface IApiRegisterRes extends IAbstractApiResponse {
access_token?: string;
user?: IUserData;
}
export interface IApiLoginRes extends IAbstractApiResponse {
access_token?: string;
}
export interface IApiUserAssetsRes extends IAbstractApiResponse {
firstName?: string;
lastName?: string;
dollarAvailables?: number;
pseudo?: string;
UserHasCrypto?: IUserWalletCryptos[];
}
export interface IApiAllTradesRes extends IAbstractApiResponse {}
export interface IAllRankRes extends IAbstractApiResponse {}
export interface IAllReferralCodeRes extends IAbstractApiResponse {}
export interface ICreateReferralCodeRes extends IAbstractApiResponse {}
export interface IReferralCodeUpdateRes extends IAbstractApiResponse {}
export interface IReferralCodeDeleteRes extends IAbstractApiResponse {}
export interface IApiAllOffersRes extends IAbstractApiResponse {
id: string
User: {
pseudo: string
}
amount: number
created_at: string
id_user: string
Crypto: ICryptoInWalletInfo
}

Some files were not shown because too many files have changed in this diff Show More