├── .gitignore ├── .npmrc ├── .stackblitzrc ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── astro.config.mjs ├── jsconfig.json ├── package.json ├── postcss.config.cjs ├── public ├── favicon.ico ├── fonts │ ├── PlusJakartaSans-Bold.woff │ ├── PlusJakartaSans-Bold.woff2 │ ├── PlusJakartaSans-BoldItalic.woff │ ├── PlusJakartaSans-BoldItalic.woff2 │ ├── PlusJakartaSans-ExtraBold.woff │ ├── PlusJakartaSans-ExtraBold.woff2 │ ├── PlusJakartaSans-ExtraBoldItalic.woff │ ├── PlusJakartaSans-ExtraBoldItalic.woff2 │ ├── PlusJakartaSans-ExtraLight.woff │ ├── PlusJakartaSans-ExtraLight.woff2 │ ├── PlusJakartaSans-ExtraLightItalic.woff │ ├── PlusJakartaSans-ExtraLightItalic.woff2 │ ├── PlusJakartaSans-Italic.woff │ ├── PlusJakartaSans-Italic.woff2 │ ├── PlusJakartaSans-Light.woff │ ├── PlusJakartaSans-Light.woff2 │ ├── PlusJakartaSans-LightItalic.woff │ ├── PlusJakartaSans-LightItalic.woff2 │ ├── PlusJakartaSans-Medium.woff │ ├── PlusJakartaSans-Medium.woff2 │ ├── PlusJakartaSans-MediumItalic.woff │ ├── PlusJakartaSans-MediumItalic.woff2 │ ├── PlusJakartaSans-Regular.woff │ ├── PlusJakartaSans-Regular.woff2 │ ├── PlusJakartaSans-SemiBold.woff │ ├── PlusJakartaSans-SemiBold.woff2 │ ├── PlusJakartaSans-SemiBoldItalic.woff │ └── PlusJakartaSans-SemiBoldItalic.woff2 └── robots.txt ├── snowpack.config.mjs ├── src ├── components │ ├── Alert.astro │ ├── Avatar.astro │ ├── Badge.astro │ ├── BaseHead.astro │ ├── Button.astro │ ├── Card.astro │ ├── Checkbox.astro │ ├── ContentSection.astro │ ├── ContentSectionTitle.astro │ ├── DataCounter.astro │ ├── Dialog.astro │ ├── IconBox.astro │ ├── IconButton.astro │ ├── Input.astro │ ├── Label.astro │ ├── Link.astro │ ├── Logo.astro │ ├── Menu.astro │ ├── Navbar.astro │ ├── NewMenu.astro │ ├── Pagination.astro │ ├── Select.astro │ ├── Sidebar.astro │ ├── SidebarHeading.astro │ ├── SidebarItem.astro │ ├── SmallPagination.astro │ ├── StatsCard.astro │ ├── TBody.astro │ ├── THead.astro │ ├── Td.astro │ ├── Textbox.astro │ └── Th.astro ├── dummies │ ├── orders.js │ ├── products.js │ └── users.js ├── layouts │ ├── AppLayout.astro │ └── AuthLayout.astro ├── pages │ ├── 404.astro │ ├── _home │ │ ├── Overview.astro │ │ ├── PopularProduct.astro │ │ └── RecentOrder.astro │ ├── auth │ │ ├── email-verification.astro │ │ ├── forgot-password.astro │ │ ├── login.astro │ │ └── register.astro │ ├── blank.astro │ ├── index.astro │ ├── profile │ │ ├── _components │ │ │ ├── BrowserSession.astro │ │ │ ├── ProfileInformation.astro │ │ │ └── UpdatePassword.astro │ │ └── index.astro │ └── users │ │ ├── _components │ │ ├── CreateUserDialog.astro │ │ ├── Filter.astro │ │ ├── Order.astro │ │ └── Table.astro │ │ └── index.astro ├── scripts │ ├── dialog.js │ ├── main.js │ ├── menu.js │ ├── popover.js │ ├── sidebar.js │ └── sidebarsub.js └── styles │ └── global.css ├── tailwind.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist 3 | 4 | # dependencies 5 | node_modules/ 6 | .snowpack/ 7 | 8 | # logs 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # environment variables 14 | .env 15 | .env.production 16 | 17 | # macOS-specific files 18 | .DS_Store 19 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ## force pnpm to hoist 2 | shamefully-hoist = true 3 | -------------------------------------------------------------------------------- /.stackblitzrc: -------------------------------------------------------------------------------- 1 | { 2 | "startCommand": "npm start", 3 | "env": { 4 | "ENABLE_CJS_IMPORTS": true 5 | } 6 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "args": [ 9 | "--extensionDevelopmentPath=${workspaceFolder}" 10 | ], 11 | "name": "Launch Extension", 12 | "outFiles": [ 13 | "${workspaceFolder}/out/**/*.js" 14 | ], 15 | "preLaunchTask": "npm", 16 | "request": "launch", 17 | "type": "pwa-extensionHost" 18 | }, 19 | ] 20 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ruine.dev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ruine UI Dashboard 2 | 3 | ![ruine UI Dashboard Preview](https://user-images.githubusercontent.com/48067039/140812271-6546da2d-51a4-4e3b-996d-8bb1e186a097.png) 4 | A responsive and accessible admin dashboard template built using ruine UI, a TailwindCSS based 5 | design system, and AlpineJS. 6 | 7 | See the demo on https://ruine-dashboard.pages.dev/ 8 | 9 | ## Installation 10 | 11 | There're two ways on how to get this template: 12 | 13 | ### Clone this repository 14 | 15 | ```bash 16 | git clone https://github.com/ruine-dev/ruine-ui-dashboard.git 17 | ``` 18 | 19 | This template is created by using [Astro](https://astro.build), check their 20 | [documentation](https://docs.astro.build/getting-started/) on how to use it. 21 | 22 | ### Download the released compiled version 23 | 24 | You could also download it from this repository 25 | [releases page](https://github.com/ruine-dev/ruine-ui-dashboard/releases). 26 | 27 | Move all the assets into your project, then replace the Tailwind config and CSS file with the one 28 | this template provides. 29 | 30 | ## Feature 31 | 32 | ### Pages 33 | 34 | These are the available pages from this template that you could use for your project: 35 | 36 | - Dashboard page 37 | - Profile page 38 | - User list page 39 | - User details page 40 | - Login page 41 | - Register page 42 | - Email verification page 43 | - Forgot password page 44 | - 404 page 45 | 46 | ### Components 47 | 48 | In those mentioned pages already included several components, such as: 49 | 50 | - Alert 51 | - Avatar 52 | - Badge 53 | - Button 54 | - Card 55 | - Checkbox 56 | - Dialog / Modal 57 | - Icon Box 58 | - Icon Button 59 | - Input & Label 60 | - Menu / Dropdown 61 | - Pagination 62 | - Popover 63 | - Select 64 | - Table 65 | 66 | ## License 67 | 68 | **ruine UI Dashboard** is licensed under [MIT](https://opensource.org/licenses/MIT). 69 | 70 | ## Special Thanks 71 | 72 | - [Astro](https://astro.build) 73 | - [TailwindCSS](https://tailwindcss.com) 74 | - [Akar Icons](https://akaricons.com) 75 | - [AlpineJS](https://alpinejs.dev) 76 | - [Unsplash](https://unsplash.com) 77 | - [Random User Generator](https://randomuser.me) 78 | -------------------------------------------------------------------------------- /astro.config.mjs: -------------------------------------------------------------------------------- 1 | // Full Astro Configuration API Documentation: 2 | // https://docs.astro.build/reference/configuration-reference 3 | 4 | // @type-check enabled! 5 | // VSCode and other TypeScript-enabled text editors will provide auto-completion, 6 | // helpful tooltips, and warnings if your exported object is invalid. 7 | // You can disable this by removing "@ts-check" and `@type` comments below. 8 | 9 | // @ts-check 10 | export default /** @type {import('astro').AstroUserConfig} */ ({ 11 | // Comment out "renderers: []" to enable Astro's default component support. 12 | renderers: [], 13 | devOptions: { 14 | tailwindConfig: './tailwind.config.js', 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "$components/*": ["src/components/*"], 6 | "$layouts/*": ["src/layouts/*"], 7 | "$dummies/*": ["src/dummies/*"], 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@example/minimal", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "astro dev", 7 | "start": "astro dev", 8 | "build": "astro build", 9 | "preview": "astro preview" 10 | }, 11 | "devDependencies": { 12 | "alpinejs": "^3.7.1", 13 | "astro": "0.20.12", 14 | "autoprefixer": "^10.4.0", 15 | "clsx": "^1.1.1", 16 | "postcss": "^8.4.5", 17 | "tailwindcss": "^3.0.7" 18 | }, 19 | "dependencies": { 20 | "@alpinejs/collapse": "^3.7.1", 21 | "@alpinejs/trap": "^3.7.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {}, 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/favicon.ico -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-Bold.woff -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-Bold.woff2 -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-BoldItalic.woff -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-BoldItalic.woff2 -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-ExtraBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-ExtraBold.woff -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-ExtraBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-ExtraBold.woff2 -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-ExtraBoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-ExtraBoldItalic.woff -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-ExtraBoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-ExtraBoldItalic.woff2 -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-ExtraLight.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-ExtraLight.woff -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-ExtraLight.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-ExtraLight.woff2 -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-ExtraLightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-ExtraLightItalic.woff -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-ExtraLightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-ExtraLightItalic.woff2 -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-Italic.woff -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-Italic.woff2 -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-Light.woff -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-Light.woff2 -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-LightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-LightItalic.woff -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-LightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-LightItalic.woff2 -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-Medium.woff -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-Medium.woff2 -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-MediumItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-MediumItalic.woff -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-MediumItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-MediumItalic.woff2 -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-Regular.woff -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-Regular.woff2 -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-SemiBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-SemiBold.woff -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-SemiBold.woff2 -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-SemiBoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-SemiBoldItalic.woff -------------------------------------------------------------------------------- /public/fonts/PlusJakartaSans-SemiBoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruine-dev/ruine-ui-dashboard/94b91fbfb0e3c591bb31ddd02478db15b3ac98f8/public/fonts/PlusJakartaSans-SemiBoldItalic.woff2 -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /snowpack.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | alias: { 3 | $components: './src/components', 4 | $layouts: './src/layouts', 5 | $dummies: './src/dummies', 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /src/components/Alert.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | 4 | const { title, className, color, ...props } = Astro.props; 5 | 6 | const colors = { 7 | success: 'bg-success-100 text-success-800 border border-success-300', 8 | danger: 'bg-danger-100 text-danger-800 border border-danger-300', 9 | warning: 'bg-warning-100 text-warning-800 border border-warning-300', 10 | info: 'bg-info-100 text-info-800 border border-info-300', 11 | }; 12 | --- 13 | -------------------------------------------------------------------------------- /src/components/Avatar.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | const { src, alt, size = 'md', shape = 'circle', className } = Astro.props; 4 | 5 | const shapes = { 6 | circle: 'rounded-full', 7 | square: 'rounded-lg', 8 | }; 9 | 10 | const sizes = { 11 | 'xs': 'w-6 h-6', 12 | 'sm': 'w-8 h-8', 13 | 'md': 'w-10 h-10', 14 | 'lg': 'w-12 h-12', 15 | 'xl': 'w-14 h-14', 16 | }; 17 | 18 | const sizeValues = { 19 | 'xs': '1.5rem', 20 | 'sm': '2rem', 21 | 'md': '2.5rem', 22 | 'lg': '3rem', 23 | 'xl': '3.5rem', 24 | } 25 | --- 26 | 27 | {alt} -------------------------------------------------------------------------------- /src/components/Badge.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | const { color, className } = Astro.props; 4 | 5 | const colors = { 6 | gray: 'bg-gray-100 text-gray-800', 7 | red: 'bg-red-100 text-red-800', 8 | green: 'bg-green-100 text-green-800', 9 | blue: 'bg-blue-100 text-blue-800', 10 | yellow: 'bg-yellow-100 text-yellow-800', 11 | purple: 'bg-purple-100 text-purple-800', 12 | pink: 'bg-pink-100 text-pink-800', 13 | indigo: 'bg-indigo-100 text-indigo-800', 14 | } 15 | --- 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/components/BaseHead.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { title } = Astro.props; 3 | --- 4 | 5 | 6 | 7 | {Boolean(title) ? {title} - ruine UI : ruine UI Dashboard} 8 | -------------------------------------------------------------------------------- /src/components/Button.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | 4 | const { as = 'button', size = 'md', color = 'white', className, ...props } = Astro.props; 5 | 6 | const getSize = (size) => { 7 | const sizes = { 8 | sm: 'py-1 px-4', 9 | md: 'py-3 sm:py-2 px-6', 10 | lg: 'py-3 px-8 text-lg', 11 | }; 12 | 13 | return sizes[size]; 14 | }; 15 | 16 | const getColor = (color) => { 17 | const noBorder = 'border border-transparent'; 18 | const noTransparent = 'ring-offset-2 shadow-sm'; 19 | const noWhiteClasses = clsx('text-white', noBorder); 20 | const noDangerClasses = 'ring-primary-600'; 21 | 22 | const colors = { 23 | primary: clsx( 24 | 'bg-primary-600 hover:bg-primary-700 ', 25 | noWhiteClasses, 26 | noDangerClasses, 27 | noTransparent, 28 | ), 29 | danger: clsx('bg-danger-600 hover:bg-danger-700 ', noWhiteClasses, noTransparent), 30 | white: clsx( 31 | 'bg-white hover:bg-gray-50 text-gray-700 border border-gray-300', 32 | noDangerClasses, 33 | noTransparent, 34 | ), 35 | transparent: clsx('hover:bg-gray-100 text-gray-700', noBorder), 36 | }; 37 | 38 | return colors[color]; 39 | }; 40 | 41 | const classes = clsx( 42 | 'disabled:opacity-60 inline-flex rounded-lg font-medium active:scale-90 transition outline-none focus:ring-2 tracking-wide disabled:cursor-not-allowed', 43 | getSize(size), 44 | getColor(color), 45 | className, 46 | ); 47 | --- 48 | {as === 'a' ? ( 49 | 50 | 51 | 52 | ) : ( 53 | 56 | )} -------------------------------------------------------------------------------- /src/components/Card.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | 4 | const { className, rounded } = Astro.props; 5 | --- 6 | 7 |
8 | 9 |
-------------------------------------------------------------------------------- /src/components/Checkbox.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | import Label from './Label.astro'; 4 | 5 | const { label, name, id, className, ...props } = Astro.props; 6 | --- 7 |
8 | 9 | 10 |
-------------------------------------------------------------------------------- /src/components/ContentSection.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | const { className, ...props } = Astro.props; 4 | --- 5 | 6 |
7 | 8 |
-------------------------------------------------------------------------------- /src/components/ContentSectionTitle.astro: -------------------------------------------------------------------------------- 1 |

-------------------------------------------------------------------------------- /src/components/DataCounter.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | 4 | const { active = 1, showing = 10, count = 10, className, ...props } = Astro.props; 5 | --- 6 | Showing {(showing * (active - 1)) + 1} to {(showing * (active - 1)) + showing} of {count} results -------------------------------------------------------------------------------- /src/components/Dialog.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | import IconButton from './IconButton.astro'; 4 | 5 | const { title, open, onClose, className, ...props } = Astro.props; 6 | --- 7 |
document.addEventListener('keydown', (event) => { 11 | if (event.key === 'Escape' && ${open}) { 12 | ${onClose} 13 | } 14 | }))`} 15 | class="fixed top-0 left-0 z-20 w-full h-full overflow-y-auto bg-gray-800/80" 16 | x-transition:enter="transition ease-out duration-300" 17 | x-transition:enter-start="opacity-0" 18 | x-transition:enter-end="opacity-100" 19 | x-transition:leave="transition ease-in duration-300" 20 | x-transition:leave-start="opacity-100" 21 | x-transition:leave-end="opacity-0" 22 | > 23 |
26 |
40 |
41 |
42 |

43 | {title} 44 |

45 | 46 | 47 | Close dialog 48 | 49 |
50 |
51 | 52 |
53 |
54 |
55 |
56 |
57 | -------------------------------------------------------------------------------- /src/components/IconBox.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | 4 | const { size = 'md' } = Astro.props; 5 | 6 | const getSize = (size) => { 7 | const sizes = { 8 | sm: 'w-8 h-8', 9 | md: 'w-12 h-12', 10 | lg: 'w-16 h-16', 11 | } 12 | 13 | return sizes[size]; 14 | } 15 | --- 16 | 17 |
21 | 22 |
-------------------------------------------------------------------------------- /src/components/IconButton.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | 4 | const { as = 'button', color = 'transparent', size = 'md', noHover, className, ...props } = Astro.props; 5 | 6 | const getSize = (size) => { 7 | const sizes = { 8 | sm: 'p-2 sm:p-1', 9 | md: 'p-3 sm:p-2', 10 | lg: 'p-3', 11 | }; 12 | 13 | return sizes[size]; 14 | }; 15 | 16 | const getColor = (color) => { 17 | const noBorder = 'border border-transparent'; 18 | const noTransparent = 'ring-offset-2 shadow-sm'; 19 | const noWhiteClasses = clsx('text-white', noBorder); 20 | const noDangerClasses = 'ring-primary-600'; 21 | 22 | const colors = { 23 | primary: clsx( 24 | 'bg-primary-600 hover:bg-primary-700 ', 25 | noWhiteClasses, 26 | noDangerClasses, 27 | noTransparent, 28 | ), 29 | danger: clsx('bg-danger-600 hover:bg-danger-700 ', noWhiteClasses, noTransparent), 30 | white: clsx( 31 | 'bg-white hover:bg-gray-50 text-gray-700 border border-gray-300', 32 | noDangerClasses, 33 | noTransparent, 34 | ), 35 | transparent: clsx('text-gray-700', noBorder, noDangerClasses, {'hover:bg-gray-100': !noHover}), 36 | }; 37 | 38 | return colors[color]; 39 | } 40 | 41 | const classes = clsx( 42 | 'inline-flex transition rounded-lg outline-none disabled:cursor-not-allowed disabled:opacity-60 active:scale-90 focus:ring-2', 43 | getSize(size), 44 | getColor(color), 45 | className 46 | ); 47 | --- 48 | {as === 'a' ? ( 49 | 50 | 51 | 52 | ) : ( 53 | 56 | )} 57 | -------------------------------------------------------------------------------- /src/components/Input.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | 4 | const { name, type, placeholder, id, className, leading, trailing, ...props } = Astro.props; 5 | const getPadding = () => { 6 | return clsx('py-3 sm:py-2', { 7 | 'pl-11 sm:pl-10 pr-3': leading, 8 | 'pl-3 pr-11 sm:pr-10': trailing, 9 | 'px-3': !leading && !trailing 10 | }); 11 | } 12 | --- 13 | 14 |
18 | {Boolean(leading) && ( 19 |
20 | {leading} 21 |
22 | )} 23 | 28 | {Boolean(trailing) && ( 29 |
30 | {trailing} 31 |
32 | )} 33 |
-------------------------------------------------------------------------------- /src/components/Label.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { htmlFor } = Astro.props; 3 | --- 4 | -------------------------------------------------------------------------------- /src/components/Link.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | 4 | const { as = 'a', color = 'primary', className, ...props } = Astro.props; 5 | 6 | const noDangerClasses = 'ring-primary-600'; 7 | 8 | const colors = { 9 | primary: clsx('text-primary-600 hover:text-primary-700', noDangerClasses), 10 | danger: clsx('text-danger-600 hover:text-danger-700 ring-danger-600'), 11 | passive: clsx('text-gray-600 hover:text-gray-700', noDangerClasses), 12 | }; 13 | 14 | const classes = clsx('font-medium outline-none focus:ring-2 rounded', colors[color], className); 15 | --- 16 | {as === 'button' ? ( 17 | 20 | ) : ( 21 | 22 | 23 | 24 | )} 25 | -------------------------------------------------------------------------------- /src/components/Logo.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | 4 | const { href, className } = Astro.props; 5 | const baseClass = clsx('font-medium tracking-tight text-gray-700', className); 6 | const subClass = clsx('font-semibold text-primary-600'); 7 | --- 8 | {Boolean(href) ? ( 9 | 10 | ruine.UI 11 | 12 | ) : ( 13 | ruine.UI 14 | )} 15 | -------------------------------------------------------------------------------- /src/components/Menu.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { items } = Astro.props; 3 | const stringItems = JSON.stringify(items); 4 | const menuItems = stringItems.replace(/\"/g, "'"); 5 | --- 6 |
7 | 8 |
14 |
15 | 45 |
46 |
47 |
-------------------------------------------------------------------------------- /src/components/Navbar.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | import Avatar from './Avatar.astro'; 4 | import Menu from './Menu.astro'; 5 | import IconButton from './IconButton.astro'; 6 | import Input from './Input.astro'; 7 | 8 | const notifications = [ 9 | { 10 | icon: , 11 | title: 'New user registered', 12 | type: 'success', 13 | time: '2 min ago', 14 | }, 15 | { 16 | icon: , 17 | title: 'New order received', 18 | type: 'info', 19 | time: '5 min ago', 20 | }, 21 | { 22 | icon: , 23 | title: 'Order #R-2384 need to be processed', 24 | type: 'warning', 25 | time: '14 min ago', 26 | }, 27 | { 28 | icon: , 29 | title: 'Order #R-2157 just got a bad review', 30 | type: 'danger', 31 | time: '18 min ago', 32 | }, 33 | { 34 | icon: , 35 | title: 'Order #R-2206 is completed', 36 | type: 'success', 37 | time: '25 min ago', 38 | }, 39 | ]; 40 | 41 | const notificationColor = { 42 | 'success': 'bg-success-100 text-success-800', 43 | 'warning': 'bg-warning-100 text-warning-800', 44 | 'danger': 'bg-danger-100 text-danger-800', 45 | 'info': 'bg-info-100 text-info-800', 46 | } 47 | 48 | const userMenu = [ 49 | [ 50 | { 51 | icon: '', 52 | label: 'Profile', 53 | href: '/profile', 54 | }, 55 | { 56 | icon: '', 57 | label: 'Settings', 58 | href: '#', 59 | } 60 | ], 61 | [ 62 | { 63 | icon: '', 64 | label: 'Log out', 65 | href: '/auth/login', 66 | } 67 | ] 68 | ]; 69 | --- 70 | 71 | 111 | -------------------------------------------------------------------------------- /src/components/NewMenu.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { itemGroups } = Astro.props; 3 | --- 4 |
5 | 6 |
12 |
13 | {itemGroups.map(group => ( 14 |
15 | {group.map(item => item.href ? ( 16 | 21 | {item.icon} 22 | {item.label} 23 | 24 | ) : ( 25 | 33 | ))} 34 |
35 | ))} 36 |
37 |
38 |
-------------------------------------------------------------------------------- /src/components/Pagination.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | import IconButton from './IconButton.astro'; 4 | 5 | const { active = 1, showing = 10, count = 10, className } = Astro.props; 6 | 7 | const range = Array(count / showing).fill(0).map((_, i) => i + 1); 8 | --- 9 |
10 | 11 | 12 | 13 | {range.map(page => ( 14 | 18 | 19 | {page} 20 | 21 | 22 | ))} 23 | ... 24 | 25 | 26 | 27 |
28 | -------------------------------------------------------------------------------- /src/components/Select.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | 4 | const { name, id, placeholder, className, ...props } = Astro.props; 5 | --- 6 | 15 | -------------------------------------------------------------------------------- /src/components/Sidebar.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import IconButton from './IconButton.astro'; 3 | import Logo from './Logo.astro'; 4 | import SidebarHeading from './SidebarHeading.astro'; 5 | import SidebarItem from './SidebarItem.astro'; 6 | 7 | const menu = [ 8 | { 9 | icon: , 10 | label: 'Dashboard', 11 | href: '/' 12 | }, 13 | { 14 | icon: , 15 | label: 'Auth', 16 | items: [ 17 | { 18 | label: 'Login', 19 | href: '/auth/login' 20 | }, 21 | { 22 | label: 'Register', 23 | href: '/auth/register' 24 | }, 25 | { 26 | label: 'Forgot Password', 27 | href: '/auth/forgot-password' 28 | }, 29 | { 30 | label: 'Email Verification', 31 | href: '/auth/email-verification' 32 | } 33 | ], 34 | }, 35 | { 36 | icon: , 37 | label: 'Miscellaneous', 38 | items: [ 39 | { 40 | label: 'Blank', 41 | href: '/blank' 42 | }, 43 | { 44 | label: '404', 45 | href: '/404' 46 | }, 47 | ] 48 | }, 49 | { 50 | icon: , 51 | label: 'Users', 52 | href: '/users' 53 | }, 54 | ]; 55 | --- 56 | 57 |
67 | -------------------------------------------------------------------------------- /src/components/SidebarHeading.astro: -------------------------------------------------------------------------------- 1 |
  • -------------------------------------------------------------------------------- /src/components/SidebarItem.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | 4 | const { href = '#', items = [] } = Astro.props; 5 | const { pathname } = Astro.request.canonicalURL; 6 | const isActive = href => href === (pathname.length > 1 ? pathname.slice(0, -1) : '/'); 7 | const hasActiveItem = items.some(item => isActive(item.href)); 8 | const classes = (href = null) => clsx('transition w-full flex font-medium block rounded-lg py-3 px-3 -ml-3 outline-none focus:ring-2 ring-primary-600', { 9 | 'hover:bg-primary-50 hover:text-primary-600': !isActive(href), 10 | 'bg-primary-100 text-primary-800': isActive(href), 11 | }); 12 | --- 13 | 14 |
  • 15 | {items.length > 0 ? ( 16 |
    17 | 21 | 40 |
    41 | ) : ( 42 | 46 | 47 | 48 | )} 49 |
  • -------------------------------------------------------------------------------- /src/components/SmallPagination.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Button from './Button.astro'; 3 | --- 4 | 5 |
    6 | 7 | 8 | 9 |
    -------------------------------------------------------------------------------- /src/components/StatsCard.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | import Card from './Card.astro'; 4 | import IconBox from './IconBox.astro'; 5 | 6 | const { label, value, icon, className } = Astro.props; 7 | --- 8 | 9 | 10 | 11 | {icon} 12 | 13 |
    14 |

    {label}

    15 |

    {value}

    16 |
    17 |
    -------------------------------------------------------------------------------- /src/components/TBody.astro: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/components/THead.astro: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/components/Td.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | 4 | const { padding = 'p-4', className } = Astro.props; 5 | --- 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/Textbox.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | import Input from './Input.astro'; 4 | import Label from './Label.astro'; 5 | 6 | const { name, id, label, className, ...props } = Astro.props; 7 | --- 8 | 9 |
    10 | 11 | 12 |
    -------------------------------------------------------------------------------- /src/components/Th.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | 4 | const { padding = 'p-4', className } = Astro.props; 5 | --- 6 | -------------------------------------------------------------------------------- /src/dummies/orders.js: -------------------------------------------------------------------------------- 1 | import { users } from './users.js'; 2 | 3 | export const orders = [ 4 | { 5 | id: 1, 6 | amount: '65.94', 7 | date: 'November 01, 2021', 8 | status: 'Pending', 9 | buyer: users[0], 10 | }, 11 | { 12 | id: 2, 13 | amount: '51.61', 14 | date: 'October 31, 2021', 15 | status: 'Pending', 16 | buyer: users[1], 17 | }, 18 | { 19 | id: 3, 20 | amount: '40.32', 21 | date: 'October 30, 2021', 22 | status: 'Processed', 23 | buyer: users[2], 24 | }, 25 | { 26 | id: 4, 27 | amount: '57.89', 28 | date: 'October 30, 2021', 29 | status: 'Canceled', 30 | buyer: users[3], 31 | }, 32 | { 33 | id: 5, 34 | amount: '90.29', 35 | date: 'October 29, 2021', 36 | status: 'Processed', 37 | buyer: users[4], 38 | }, 39 | { 40 | id: 6, 41 | amount: '94.93', 42 | date: 'October 29, 2021', 43 | status: 'Completed', 44 | buyer: users[5], 45 | }, 46 | { 47 | id: 7, 48 | amount: '66.65', 49 | date: 'October 29, 2021', 50 | status: 'Completed', 51 | buyer: users[6], 52 | }, 53 | { 54 | id: 8, 55 | amount: '65.52', 56 | date: 'October 27, 2021', 57 | status: 'Canceled', 58 | buyer: users[7], 59 | }, 60 | { 61 | id: 9, 62 | amount: '74.66', 63 | date: 'October 26, 2021', 64 | status: 'Completed', 65 | buyer: users[8], 66 | }, 67 | { 68 | id: 10, 69 | amount: '35.38', 70 | date: 'October 26, 2021', 71 | status: 'Completed', 72 | buyer: users[9], 73 | }, 74 | ]; 75 | 76 | export const statuses = { 77 | Pending: 'yellow', 78 | Processed: 'blue', 79 | Completed: 'green', 80 | Canceled: 'gray', 81 | }; 82 | -------------------------------------------------------------------------------- /src/dummies/products.js: -------------------------------------------------------------------------------- 1 | export const products = [ 2 | { 3 | id: 1, 4 | name: 'Black Canon Camera', 5 | price: 350, 6 | rating: 4.3, 7 | sold: 352, 8 | image: 'https://source.unsplash.com/W2Dta_Yiwfw', 9 | }, 10 | { 11 | id: 2, 12 | name: 'White Wireless Earphone', 13 | price: 150, 14 | rating: 5.0, 15 | sold: 341, 16 | image: 'https://source.unsplash.com/SBLT7JohtCo', 17 | }, 18 | { 19 | id: 3, 20 | name: 'Black Headset', 21 | price: 130, 22 | rating: 3.9, 23 | sold: 295, 24 | image: 'https://source.unsplash.com/dBwadhWa-lI', 25 | }, 26 | { 27 | id: 4, 28 | name: 'Playstation 5', 29 | price: 500, 30 | rating: 4.5, 31 | sold: 288, 32 | image: 'https://source.unsplash.com/dUx0gwLbhzs', 33 | }, 34 | { 35 | id: 5, 36 | name: 'White Keyboard', 37 | price: 200, 38 | rating: 5.0, 39 | sold: 196, 40 | image: 'https://source.unsplash.com/Ac9L5MrIPUQ', 41 | }, 42 | ]; 43 | -------------------------------------------------------------------------------- /src/dummies/users.js: -------------------------------------------------------------------------------- 1 | export const users = [ 2 | { 3 | id: 1, 4 | avatar: `https://randomuser.me/api/portraits/women/4.jpg`, 5 | name: 'Bealle Frankland', 6 | email: 'bealles-frankland@example.com', 7 | role: 'customer', 8 | status: 'active', 9 | created_at: 'November 07, 2020', 10 | }, 11 | { 12 | id: 2, 13 | avatar: `https://randomuser.me/api/portraits/women/8.jpg`, 14 | name: 'Kelsi Kohrt', 15 | email: 'kelsi-kohrt@example.com', 16 | role: 'customer', 17 | status: 'inactive', 18 | created_at: 'November 02, 2020', 19 | }, 20 | { 21 | id: 3, 22 | avatar: `https://randomuser.me/api/portraits/men/12.jpg`, 23 | name: 'Denny Marfell', 24 | email: 'denny-marfell@example.com', 25 | role: 'customer', 26 | status: 'active', 27 | created_at: 'October 31, 2020', 28 | }, 29 | { 30 | id: 4, 31 | avatar: `https://randomuser.me/api/portraits/women/16.jpg`, 32 | name: 'Dede Iowarch', 33 | email: 'dede-iowarch@example.com', 34 | role: 'customer', 35 | status: 'active', 36 | created_at: 'October 31, 2020', 37 | }, 38 | { 39 | id: 5, 40 | avatar: `https://randomuser.me/api/portraits/women/20.jpg`, 41 | name: 'Carmelia Bushill', 42 | email: 'carmelia-bushill@example.com', 43 | role: 'customer', 44 | status: 'inactive', 45 | created_at: 'October 24, 2020', 46 | }, 47 | { 48 | id: 6, 49 | avatar: `https://randomuser.me/api/portraits/men/24.jpg`, 50 | name: 'Afton Maior', 51 | email: 'afton-maior@example.com', 52 | role: 'admin', 53 | status: 'active', 54 | created_at: 'October 16, 2020', 55 | }, 56 | { 57 | id: 7, 58 | avatar: `https://randomuser.me/api/portraits/women/28.jpg`, 59 | name: 'Eula Issacson', 60 | email: 'eula-issacson@example.com', 61 | role: 'customer', 62 | status: 'active', 63 | created_at: 'September 29, 2020', 64 | }, 65 | { 66 | id: 8, 67 | avatar: `https://randomuser.me/api/portraits/women/32.jpg`, 68 | name: 'Javier Blabber', 69 | email: 'javier-blabber@example.com', 70 | role: 'admin', 71 | status: 'active', 72 | created_at: 'September 29, 2020', 73 | }, 74 | { 75 | id: 9, 76 | avatar: `https://randomuser.me/api/portraits/men/36.jpg`, 77 | name: 'Nikolaos Ciotto', 78 | email: 'nikolaos-ciotto@example.com', 79 | role: 'customer', 80 | status: 'active', 81 | created_at: 'September 15, 2020', 82 | }, 83 | { 84 | id: 10, 85 | avatar: `https://randomuser.me/api/portraits/men/40.jpg`, 86 | name: 'Delbert McIlharga', 87 | email: 'delbert-mcilharga@example.com', 88 | role: 'customer', 89 | status: 'inactive', 90 | created_at: 'August 21, 2020', 91 | }, 92 | ]; 93 | 94 | export const statuses = { 95 | active: 'green', 96 | inactive: 'red', 97 | }; 98 | -------------------------------------------------------------------------------- /src/layouts/AppLayout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import BaseHead from '$components/BaseHead.astro'; 3 | import Navbar from '$components/Navbar.astro'; 4 | import Sidebar from '$components/Sidebar.astro'; 5 | 6 | const { title, ...props } = Astro.props; 7 | 8 | const sidebarOptions = JSON.stringify({ 9 | noScrollClass: 'overflow-y-hidden', 10 | noScrollBreakpoint: 1024, 11 | }).replace(/\"/g, "'"); 12 | --- 13 | 14 | 15 | 16 | 17 | 18 | 19 |
    20 | 21 |
    22 |
    23 | {Boolean(title) &&

    {title}

    } 24 | 25 |
    26 |
    27 | 28 |
    29 |
    30 |
    31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/layouts/AuthLayout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import clsx from 'clsx'; 3 | import BaseHead from '$components/BaseHead.astro'; 4 | import Logo from '$components/Logo.astro'; 5 | 6 | const { title, background, right = false } = Astro.props; 7 | --- 8 | 9 | 10 | 11 | 12 | 13 |
    14 |
    15 | 16 |

    {title}

    17 |
    18 | 19 |
    20 |
    21 |
    22 |

    © 2021 all rights reserved

    23 |
    24 |
    25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/pages/404.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import BaseHead from '$components/BaseHead.astro'; 3 | import Button from '$components/Button.astro'; 4 | import Link from '$components/Link.astro'; 5 | import Logo from '$components/Logo.astro'; 6 | --- 7 | 8 | 9 | 10 | 11 | 12 |
    13 | 14 |

    404

    15 |

    Page not found

    16 |

    Oops! The page you're looking for is nowhere to be found

    17 | 18 |

    Or back to previous page

    19 |
    20 | 21 | 22 | -------------------------------------------------------------------------------- /src/pages/_home/Overview.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import ContentSection from '$components/ContentSection.astro'; 3 | import ContentSectionTitle from '$components/ContentSectionTitle.astro'; 4 | import StatsCard from '$components/StatsCard.astro'; 5 | 6 | const { className } = Astro.props; 7 | 8 | const stats = [ 9 | { 10 | label: 'Account balance', 11 | value: '$ 21,853', 12 | icon: , 13 | }, 14 | { 15 | label: 'Total users', 16 | value: '1,279', 17 | icon: , 18 | }, 19 | { 20 | label: 'Listed products', 21 | value: '358', 22 | icon: , 23 | }, 24 | { 25 | label: 'Completed orders', 26 | value: '604', 27 | icon: , 28 | }, 29 | ]; 30 | --- 31 | 32 | 33 | Overview 34 |
    35 | {stats.map(({label, value, icon}) => ( 36 | 37 | ))} 38 |
    39 |
    -------------------------------------------------------------------------------- /src/pages/_home/PopularProduct.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Avatar from '$components/Avatar.astro'; 3 | import Card from '$components/Card.astro'; 4 | import ContentSection from '$components/ContentSection.astro'; 5 | import ContentSectionTitle from '$components/ContentSectionTitle.astro'; 6 | import { products } from '$dummies/products.js'; 7 | 8 | const { className } = Astro.props; 9 | --- 10 | 11 | 12 |
    13 | Popular Products 14 | 15 | 20 |
    21 | 22 |
      23 | {products.map(({ name, price, sold, rating, image }) => ( 24 |
    • 25 |
      26 | 27 |
      28 |
      29 |

      {name}

      30 | $ {price} USD 31 |
      32 |
      33 |

      34 | {sold} units sold 35 |

      36 |

      37 | {rating} / 5 38 | 39 |

      40 |
      41 |
      42 |
      43 |
    • 44 | ))} 45 |
    46 |
    47 |
    -------------------------------------------------------------------------------- /src/pages/_home/RecentOrder.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Avatar from '$components/Avatar.astro'; 3 | import Badge from '$components/Badge.astro'; 4 | import Card from '$components/Card.astro'; 5 | import ContentSection from '$components/ContentSection.astro'; 6 | import ContentSectionTitle from '$components/ContentSectionTitle.astro'; 7 | import Link from '$components/Link.astro'; 8 | import TBody from '$components/TBody.astro'; 9 | import Td from '$components/Td.astro'; 10 | import Th from '$components/Th.astro'; 11 | import THead from '$components/THead.astro'; 12 | import { orders, statuses } from '$dummies/orders.js'; 13 | 14 | const { className } = Astro.props; 15 | 16 | const shownOrders = orders.slice(0, 5); 17 | --- 18 | 19 |
    20 | Recent Orders 21 | View all 22 |
    23 | 24 | 25 | 26 | 27 | 30 | 33 | 36 | 39 | 40 | 41 | 42 | {shownOrders.map(({ buyer: {avatar, name, email}, amount, status, date }) => ( 43 | 44 | 53 | 56 | 59 | 62 | 63 | ))} 64 | 65 | 66 |
    67 |
      68 | {shownOrders.map(({ buyer: {avatar, name, email}, amount, date, status }) => ( 69 |
    • 70 |
      71 | {date} 72 | {status} 73 |
      74 |
      75 |
      76 | 77 |
      78 |

      {name}

      79 |

      {email}

      80 |
      81 |
      82 | $ {amount} USD 83 |
      84 |
    • 85 | ))} 86 |
    87 |
    88 |
    89 |
    -------------------------------------------------------------------------------- /src/pages/auth/email-verification.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import AuthLayout from '$layouts/AuthLayout.astro'; 3 | import Button from '$components/Button.astro'; 4 | --- 5 | 6 | 7 |
    8 |

    Thanks for signing up!

    9 |

    Before getting started, could you verify your email address by clicking on the link we just emailed to you?

    10 |

    If you didn't receive the email, we will gladly send you another.

    11 |
    12 |
    13 | 14 |
    15 |

    16 | Log out 17 |

    18 |
    -------------------------------------------------------------------------------- /src/pages/auth/forgot-password.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import AuthLayout from '$layouts/AuthLayout.astro'; 3 | import Button from '$components/Button.astro'; 4 | import Textbox from '$components/Textbox.astro'; 5 | --- 6 | 7 | 8 |
    9 |

    Forgot your password? No problem.

    10 |

    Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.

    11 |
    12 |
    13 | 20 | 21 | 22 |
    -------------------------------------------------------------------------------- /src/pages/auth/login.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import AuthLayout from '$layouts/AuthLayout.astro'; 3 | import Button from '$components/Button.astro'; 4 | import Checkbox from '$components/Checkbox.astro'; 5 | import Link from '$components/Link.astro'; 6 | import Textbox from '$components/Textbox.astro'; 7 | --- 8 | 9 | 10 |
    11 | 18 | 25 |
    26 | 30 | 31 | Forgot password? 32 | 33 |
    34 | 35 | 36 |

    37 | Not registered yet? 38 | 39 | Create an account 40 | 41 |

    42 |
    -------------------------------------------------------------------------------- /src/pages/auth/register.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import AuthLayout from '$layouts/AuthLayout.astro'; 3 | import Button from '$components/Button.astro'; 4 | import Link from '$components/Link.astro'; 5 | import Textbox from '$components/Textbox.astro'; 6 | --- 7 | 8 | 9 |
    10 | 16 | 23 | 30 | 37 | 38 | 39 |

    40 | Already registered? 41 | 42 | Login 43 | 44 |

    45 |
    -------------------------------------------------------------------------------- /src/pages/blank.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import AppLayout from '$layouts/AppLayout.astro'; 3 | --- 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import AppLayout from '$layouts/AppLayout.astro'; 3 | import Overview from './_home/Overview.astro'; 4 | import RecentOrder from './_home/RecentOrder.astro'; 5 | import PopularProduct from './_home/PopularProduct.astro'; 6 | --- 7 | 8 | 9 |
    10 | 11 |
    12 | 13 | 14 |
    15 |
    16 |
    17 | -------------------------------------------------------------------------------- /src/pages/profile/_components/BrowserSession.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Button from '$components/Button.astro'; 3 | import ContentSection from '$components/ContentSection.astro'; 4 | import ContentSectionTitle from '$components/ContentSectionTitle.astro'; 5 | --- 6 | 7 |
    8 | Browser Sessions 9 |

    10 | Manage and log out your active sessions on other browsers and devices. 11 |

    12 |
    13 |

    14 | If necessary, you may log out of all of your other browser sessions across all of your devices. 15 |

    16 |

    17 | Some of your recent sessions are listed below; however, this list may not be exhaustive. If you feel your account has been compromised, you should also update your password. 18 |

    19 | 20 |
    -------------------------------------------------------------------------------- /src/pages/profile/_components/ProfileInformation.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Button from '$components/Button.astro'; 3 | import ContentSection from '$components/ContentSection.astro'; 4 | import ContentSectionTitle from '$components/ContentSectionTitle.astro'; 5 | import Textbox from '$components/Textbox.astro'; 6 | --- 7 | 8 |
    9 |
    13 | Lisa Greyrat's Avatar 18 | 21 | 41 |
    42 |
    43 |
    44 | Profile Information 45 |

    46 | Update your account's profile information and email address. 47 |

    48 |
    49 |
    50 | 56 | 63 |
    64 | 65 |
    66 |
    67 |
    68 |
    69 |
    -------------------------------------------------------------------------------- /src/pages/profile/_components/UpdatePassword.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Button from '$components/Button.astro'; 3 | import ContentSection from '$components/ContentSection.astro'; 4 | import ContentSectionTitle from '$components/ContentSectionTitle.astro'; 5 | import Textbox from '$components/Textbox.astro'; 6 | --- 7 | 8 |
    9 | Update Password 10 |

    11 | Ensure your account is using a long, random password to stay secure. 12 |

    13 |
    14 |
    15 | 21 | 27 | 33 |
    34 | 35 |
    36 | 37 |
    -------------------------------------------------------------------------------- /src/pages/profile/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import AppLayout from '$layouts/AppLayout.astro'; 3 | import BrowserSession from './_components/BrowserSession.astro'; 4 | import ProfileInformation from './_components/ProfileInformation.astro'; 5 | import UpdatePassword from './_components/UpdatePassword.astro'; 6 | --- 7 | 8 | 9 |
    10 | 11 |
    12 | 13 | 14 |
    15 |
    16 |
    -------------------------------------------------------------------------------- /src/pages/users/_components/CreateUserDialog.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Alert from '$components/Alert.astro'; 3 | import Button from '$components/Button.astro'; 4 | import Dialog from '$components/Dialog.astro'; 5 | import Link from '$components/Link.astro'; 6 | import Textbox from '$components/Textbox.astro'; 7 | --- 8 | 14 |
    15 | 21 | 28 | 35 | 42 | 43 | The created user will automatically have a role of admin. 44 | 45 |
    46 | Cancel 47 | 48 |
    49 | 50 |
    -------------------------------------------------------------------------------- /src/pages/users/_components/Filter.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Input from '$components/Input.astro'; 3 | import Select from '$components/Select.astro'; 4 | --- 5 |
    6 |
    7 | Filter user 8 |
    9 | 10 | } 16 | /> 17 |
    18 |
    19 | 20 | 25 |
    26 |
    27 | 28 | 33 |
    34 |
    35 |
    -------------------------------------------------------------------------------- /src/pages/users/_components/Order.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Link from '$components/Link.astro'; 3 | --- 4 |
    8 |
    9 | 10 | 18 |
    19 |
    20 | 21 | 31 | 41 |
    42 | Reset 43 |
    -------------------------------------------------------------------------------- /src/pages/users/_components/Table.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Avatar from '$components/Avatar.astro'; 3 | import Badge from '$components/Badge.astro'; 4 | import Card from '$components/Card.astro'; 5 | import IconButton from '$components/IconButton.astro'; 6 | import Td from '$components/Td.astro'; 7 | import TBody from '$components/TBody.astro'; 8 | import Th from '$components/Th.astro'; 9 | import THead from '$components/THead.astro'; 10 | import { users, statuses } from '$dummies/users'; 11 | --- 12 | 13 |
    14 | 15 | 16 | 17 | 20 | 23 | 26 | 29 | 31 | 32 | 33 | {users.map(({avatar, name, email, role, status, created_at}) => ( 34 | 35 | 44 | 45 | 48 | 51 | 57 | 58 | ))} 59 | 60 |
    18 | Name 19 | 21 | Role 22 | 24 | Status 25 | 27 | Registration date 28 | 30 |
    36 |
    37 | 38 |
    39 |

    {name}

    40 |

    {email}

    41 |
    42 |
    43 |
    {role} 46 | {status} 47 | 49 | {created_at} 50 | 52 | 53 | 54 | Edit User 55 | 56 |
    61 |
    62 |
    -------------------------------------------------------------------------------- /src/pages/users/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import AppLayout from '$layouts/AppLayout.astro'; 3 | import Button from '$components/Button.astro'; 4 | import DataCounter from '$components/DataCounter.astro'; 5 | import Pagination from '$components/Pagination.astro'; 6 | import SmallPagination from '$components/SmallPagination.astro'; 7 | import CreateUserDialog from './_components/CreateUserDialog.astro'; 8 | import Filter from './_components/Filter.astro'; 9 | import Order from './_components/Order.astro'; 10 | import Table from './_components/Table.astro'; 11 | --- 12 | 13 | 18 | 19 |
    20 |
    21 | 22 |
    23 | 24 | 25 |
    26 |
    27 | 28 | 31 |
    32 | 33 |
    34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/scripts/dialog.js: -------------------------------------------------------------------------------- 1 | export default function dialog() { 2 | return { 3 | id: Math.random().toString(36).substr(2, 9), 4 | 5 | dialog: { 6 | [':role']() { 7 | return 'dialog'; 8 | }, 9 | [':aria-modal']() { 10 | return true; 11 | }, 12 | [':aria-labelledby']() { 13 | return `dialog-title-${this.id}`; 14 | } 15 | }, 16 | 17 | dialogTitle: { 18 | [':id']() { 19 | return `dialog-title-${this.id}`; 20 | }, 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/scripts/main.js: -------------------------------------------------------------------------------- 1 | import Alpine from 'alpinejs'; 2 | import trap from '@alpinejs/trap'; 3 | import collapse from '@alpinejs/collapse'; 4 | import dialog from './dialog.js'; 5 | import menu from './menu.js'; 6 | import popover from './popover.js'; 7 | import sidebar from './sidebar.js'; 8 | import sidebarSub from './sidebarsub.js'; 9 | 10 | Alpine.plugin(trap); 11 | Alpine.plugin(collapse); 12 | 13 | Alpine.data('dialog', dialog); 14 | Alpine.data('menu', menu); 15 | Alpine.data('popover', popover); 16 | Alpine.data('sidebar', sidebar); 17 | Alpine.data('sidebarSub', sidebarSub); 18 | 19 | Alpine.start(); 20 | -------------------------------------------------------------------------------- /src/scripts/menu.js: -------------------------------------------------------------------------------- 1 | export default function menu(menuItems = []) { 2 | return { 3 | id: Math.random().toString(36).substr(2, 9), 4 | open: false, 5 | active: null, 6 | menuItems: addIndexToEachMenuItem(menuItems), 7 | length: menuItems.flat().length, 8 | 9 | init() { 10 | this.$nextTick(() => { 11 | const menuItems = this.$refs.menuList.querySelectorAll('[role=menuitem]'); 12 | this.$watch('active', value => { 13 | if (value !== null) { 14 | menuItems[value - 1].focus(); 15 | } else { 16 | this.$refs.menuList.focus(); 17 | } 18 | }); 19 | }); 20 | }, 21 | 22 | close() { 23 | this.open = false; 24 | this.active = null; 25 | }, 26 | 27 | toggle() { 28 | if (this.open) { 29 | this.active = null; 30 | this.$nextTick(() => { 31 | this.$refs.menuButton.focus(); 32 | }); 33 | } 34 | 35 | this.open = !this.open; 36 | }, 37 | 38 | focusPreviousMenu() { 39 | if (this.active === null) { 40 | this.$refs.menuButton.blur(); 41 | this.active = this.length; 42 | return; 43 | } 44 | if (this.active === 1) { 45 | return; 46 | } 47 | this.active -= 1; 48 | }, 49 | 50 | focusNextMenu() { 51 | if (this.active === null) { 52 | this.$refs.menuButton.blur(); 53 | this.active = 1; 54 | return; 55 | } 56 | if (this.active === this.length) { 57 | return; 58 | } 59 | this.active += 1; 60 | }, 61 | 62 | menu: { 63 | ['@click.outside']() { 64 | this.close(); 65 | }, 66 | ['@keydown.escape']() { 67 | this.close(); 68 | this.$nextTick(() => { 69 | this.$refs.menuButton.focus(); 70 | }); 71 | }, 72 | }, 73 | 74 | menuButton: { 75 | ['@click']() { 76 | this.toggle(); 77 | this.$nextTick(() => { 78 | this.$refs.menuList.focus(); 79 | }); 80 | }, 81 | ['@keydown.space']() { 82 | this.$nextTick(() => { 83 | this.$refs.menuList.querySelector('[role=menuitem]').focus(); 84 | }); 85 | }, 86 | ['@keydown.enter']() { 87 | this.$nextTick(() => { 88 | this.$refs.menuList.querySelector('[role=menuitem]').focus(); 89 | }); 90 | }, 91 | [':id']() { 92 | return `menu-button-${this.id}`; 93 | }, 94 | [':aria-haspopup']() { 95 | return true; 96 | }, 97 | [':aria-expanded']() { 98 | return this.open; 99 | }, 100 | [':aria-controls']() { 101 | return `menu-items-${this.id}`; 102 | }, 103 | }, 104 | 105 | menuList: { 106 | ['x-show']() { 107 | return this.open; 108 | }, 109 | ['@keydown.tab.prevent']() {}, 110 | ['@keydown.arrow-up.prevent']() { 111 | this.focusPreviousMenu(); 112 | }, 113 | ['@keydown.arrow-down.prevent']() { 114 | this.focusNextMenu(); 115 | }, 116 | [':role']() { 117 | return 'menu'; 118 | }, 119 | [':id']() { 120 | return `menu-items-${this.id}`; 121 | }, 122 | [':aria-labelledby']() { 123 | return `menu-button-${this.id}`; 124 | }, 125 | [':tabindex']() { 126 | return 0; 127 | }, 128 | }, 129 | 130 | menuItem: { 131 | ['@keydown.tab.prevent']() {}, 132 | ['@keydown.arrow-up.prevent.stop']() { 133 | this.focusPreviousMenu(); 134 | }, 135 | ['@keydown.arrow-down.prevent.stop']() { 136 | this.focusNextMenu(); 137 | }, 138 | ['@mouseleave']() { 139 | this.active = null; 140 | }, 141 | ['@click']() { 142 | this.close(); 143 | }, 144 | [':role']() { 145 | return 'menuitem'; 146 | }, 147 | [':tabindex']() { 148 | return -1; 149 | }, 150 | }, 151 | }; 152 | } 153 | 154 | const addIndexToEachMenuItem = menuItems => { 155 | let index = 0; 156 | 157 | const flatMenuItemsWithArrayIndex = menuItems.flatMap((menuItem, arrIndex) => { 158 | return menuItem.map(item => { 159 | index++; 160 | return { 161 | ...item, 162 | arrIndex, 163 | index, 164 | }; 165 | }); 166 | }); 167 | 168 | return flatMenuItemsWithArrayIndex.reduce((acc, item) => { 169 | const { arrIndex } = item; 170 | if (!acc[arrIndex]) { 171 | acc[arrIndex] = []; 172 | } 173 | acc[arrIndex].push(item); 174 | return acc; 175 | }, []); 176 | }; 177 | -------------------------------------------------------------------------------- /src/scripts/popover.js: -------------------------------------------------------------------------------- 1 | export default function popover() { 2 | return { 3 | id: Math.random().toString(36).substr(2, 9), 4 | open: false, 5 | 6 | close() { 7 | this.open = false; 8 | }, 9 | 10 | toggle() { 11 | if (this.open) { 12 | this.active = null; 13 | this.$nextTick(() => { 14 | this.$refs.popoverButton.focus(); 15 | }); 16 | } 17 | 18 | this.open = !this.open; 19 | }, 20 | 21 | popover: { 22 | ['x-trap']() { 23 | return this.open; 24 | }, 25 | ['@keydown.escape']() { 26 | this.close(); 27 | }, 28 | ['@click.outside']() { 29 | this.close(); 30 | }, 31 | }, 32 | 33 | popoverButton: { 34 | ['@click']() { 35 | this.toggle(); 36 | }, 37 | [':id']() { 38 | return `popover-button-${this.id}`; 39 | }, 40 | [':aria-expanded']() { 41 | return this.open; 42 | }, 43 | [':aria-controls']() { 44 | return `popover-panel-${this.id}`; 45 | }, 46 | }, 47 | 48 | popoverPanel: { 49 | ['x-show']() { 50 | return this.open; 51 | }, 52 | [':id']() { 53 | return `popover-panel-${this.id}`; 54 | }, 55 | }, 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /src/scripts/sidebar.js: -------------------------------------------------------------------------------- 1 | export default function sidebar( 2 | options = { noScroll: false, noScrollClass: '', noScrollBreakpoint: 0 } 3 | ) { 4 | const { noScroll, noScrollClass, noScrollBreakpoint } = options; 5 | const isLowerThanScrollBreakpoint = () => window.innerWidth < noScrollBreakpoint; 6 | 7 | return { 8 | open: true, 9 | trap: false, 10 | 11 | init() { 12 | this.$nextTick(() => { 13 | if (isLowerThanScrollBreakpoint()) { 14 | this.open = false; 15 | } 16 | this.$watch('open', () => { 17 | if (this.open) { 18 | document.documentElement.classList.add(noScrollClass); 19 | } else { 20 | document.documentElement.classList.remove(noScrollClass); 21 | } 22 | }); 23 | }); 24 | }, 25 | 26 | toggle() { 27 | if (!this.open) { 28 | this.$nextTick(() => { 29 | if (isLowerThanScrollBreakpoint()) { 30 | this.trap = true; 31 | } 32 | // TODO: remove this hack when alpine fixes the bug 33 | setTimeout(() => { 34 | this.$refs.sidebarMenu.querySelector('button:not([x-bind="sidebarClose"]), [href], input, select, textarea').focus(); 35 | }, 50); 36 | }); 37 | } 38 | 39 | this.open = !this.open; 40 | }, 41 | 42 | close() { 43 | this.open = false; 44 | this.trap = false; 45 | this.$refs.sidebarButton.focus(); 46 | }, 47 | 48 | sidebarButton: { 49 | ['@click']() { 50 | this.toggle(); 51 | }, 52 | [':aria-controls']() { 53 | return 'sidebar'; 54 | }, 55 | [':aria-expanded']() { 56 | return this.open; 57 | }, 58 | }, 59 | 60 | sidebarClose: { 61 | ['@click']() { 62 | this.close(); 63 | }, 64 | }, 65 | 66 | sidebarMenu: { 67 | ['x-show']() { 68 | return this.open; 69 | }, 70 | [noScroll ? 'x-trap.noscroll' : 'x-trap']() { 71 | return this.trap; 72 | }, 73 | ['@keydown.escape.document']() { 74 | if (isLowerThanScrollBreakpoint()) { 75 | this.close(); 76 | } 77 | }, 78 | ['@resize.window']() { 79 | if (isLowerThanScrollBreakpoint()) { 80 | this.trap = true; 81 | } else { 82 | this.trap = false; 83 | } 84 | }, 85 | [':id']() { 86 | return 'sidebar'; 87 | }, 88 | }, 89 | 90 | sidebarOverlay: { 91 | ['x-show']() { 92 | return this.open; 93 | }, 94 | ['@click']() { 95 | this.close(); 96 | }, 97 | }, 98 | }; 99 | } 100 | -------------------------------------------------------------------------------- /src/scripts/sidebarsub.js: -------------------------------------------------------------------------------- 1 | export default function sidebarSub(initialOpen) { 2 | return { 3 | id: Math.random().toString(36).substr(2, 9), 4 | open: initialOpen, 5 | 6 | toggle() { 7 | this.open = !this.open; 8 | }, 9 | 10 | sidebarSubButton: { 11 | ['@click']() { 12 | this.toggle(); 13 | }, 14 | [':aria-controls']() { 15 | return `sidebar-sub-menu-${this.id}`; 16 | }, 17 | [':aria-expanded']() { 18 | return this.open; 19 | }, 20 | }, 21 | 22 | sidebarSubMenu: { 23 | ['x-show']() { 24 | return this.open; 25 | }, 26 | // TODO: uncomment below code after alpine fix focus bug 27 | // ['x-collapse']() {}, 28 | [':id']() { 29 | return `sidebar-sub-menu-${this.id}`; 30 | }, 31 | }, 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /src/styles/global.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | html { 7 | scroll-behavior: smooth; 8 | } 9 | 10 | @font-face { 11 | font-family: 'Plus Jakarta Sans'; 12 | font-style: normal; 13 | font-weight: 200; 14 | font-display: swap; 15 | src: url('/fonts/PlusJakartaSans-ExtraLight.woff2') format('woff2'), 16 | url('/fonts/PlusJakartaSans-ExtraLight.woff') format('woff'); 17 | } 18 | 19 | @font-face { 20 | font-family: 'Plus Jakarta Sans'; 21 | font-style: italic; 22 | font-weight: 200; 23 | font-display: swap; 24 | src: url('/fonts/PlusJakartaSans-ExtraLightItalic.woff2') format('woff2'), 25 | url('/fonts/PlusJakartaSans-ExtraLightItalic.woff') format('woff'); 26 | } 27 | 28 | @font-face { 29 | font-family: 'Plus Jakarta Sans'; 30 | font-style: normal; 31 | font-weight: 300; 32 | font-display: swap; 33 | src: url('/fonts/PlusJakartaSans-Light.woff2') format('woff2'), 34 | url('/fonts/PlusJakartaSans-Light.woff') format('woff'); 35 | } 36 | 37 | @font-face { 38 | font-family: 'Plus Jakarta Sans'; 39 | font-style: italic; 40 | font-weight: 300; 41 | font-display: swap; 42 | src: url('/fonts/PlusJakartaSans-LightItalic.woff2') format('woff2'), 43 | url('/fonts/PlusJakartaSans-LightItalic.woff') format('woff'); 44 | } 45 | 46 | @font-face { 47 | font-family: 'Plus Jakarta Sans'; 48 | font-style: normal; 49 | font-weight: 400; 50 | font-display: swap; 51 | src: url('/fonts/PlusJakartaSans-Regular.woff2') format('woff2'), 52 | url('/fonts/PlusJakartaSans-Regular.woff') format('woff'); 53 | } 54 | 55 | @font-face { 56 | font-family: 'Plus Jakarta Sans'; 57 | font-style: italic; 58 | font-weight: 400; 59 | font-display: swap; 60 | src: url('/fonts/PlusJakartaSans-RegularItalic.woff2') format('woff2'), 61 | url('/fonts/PlusJakartaSans-RegularItalic.woff') format('woff'); 62 | } 63 | 64 | @font-face { 65 | font-family: 'Plus Jakarta Sans'; 66 | font-style: normal; 67 | font-weight: 500; 68 | font-display: swap; 69 | src: url('/fonts/PlusJakartaSans-Medium.woff2') format('woff2'), 70 | url('/fonts/PlusJakartaSans-Medium.woff') format('woff'); 71 | } 72 | 73 | @font-face { 74 | font-family: 'Plus Jakarta Sans'; 75 | font-style: italic; 76 | font-weight: 500; 77 | font-display: swap; 78 | src: url('/fonts/PlusJakartaSans-MediumItalic.woff2') format('woff2'), 79 | url('/fonts/PlusJakartaSans-MediumItalic.woff') format('woff'); 80 | } 81 | 82 | @font-face { 83 | font-family: 'Plus Jakarta Sans'; 84 | font-style: normal; 85 | font-weight: 600; 86 | font-display: swap; 87 | src: url('/fonts/PlusJakartaSans-SemiBold.woff2') format('woff2'), 88 | url('/fonts/PlusJakartaSans-SemiBold.woff') format('woff'); 89 | } 90 | 91 | @font-face { 92 | font-family: 'Plus Jakarta Sans'; 93 | font-style: italic; 94 | font-weight: 600; 95 | font-display: swap; 96 | src: url('/fonts/PlusJakartaSans-SemiBoldItalic.woff2') format('woff2'), 97 | url('/fonts/PlusJakartaSans-SemiBoldItalic.woff') format('woff'); 98 | } 99 | 100 | @font-face { 101 | font-family: 'Plus Jakarta Sans'; 102 | font-style: normal; 103 | font-weight: 700; 104 | font-display: swap; 105 | src: url('/fonts/PlusJakartaSans-Bold.woff2') format('woff2'), 106 | url('/fonts/PlusJakartaSans-Bold.woff') format('woff'); 107 | } 108 | 109 | @font-face { 110 | font-family: 'Plus Jakarta Sans'; 111 | font-style: italic; 112 | font-weight: 700; 113 | font-display: swap; 114 | src: url('/fonts/PlusJakartaSans-BoldItalic.woff2') format('woff2'), 115 | url('/fonts/PlusJakartaSans-BoldItalic.woff') format('woff'); 116 | } 117 | 118 | @font-face { 119 | font-family: 'Plus Jakarta Sans'; 120 | font-style: normal; 121 | font-weight: 800; 122 | font-display: swap; 123 | src: url('/fonts/PlusJakartaSans-ExtraBold.woff2') format('woff2'), 124 | url('/fonts/PlusJakartaSans-ExtraBold.woff') format('woff'); 125 | } 126 | 127 | @font-face { 128 | font-family: 'Plus Jakarta Sans'; 129 | font-style: italic; 130 | font-weight: 800; 131 | font-display: swap; 132 | src: url('/fonts/PlusJakartaSans-ExtraBoldItalic.woff2') format('woff2'), 133 | url('/fonts/PlusJakartaSans-ExtraBoldItalic.woff') format('woff'); 134 | } 135 | 136 | [type='checkbox']:checked { 137 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23FFFFFF' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12l6 6L20 6'/%3E%3C/svg%3E"); 138 | background-size: 0.75rem; 139 | @apply bg-center bg-no-repeat; 140 | } 141 | 142 | select:not(.reset) { 143 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236B7280%0A' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 17l4 4 4-4'/%3E%3Cpath d='M8 7l4-4 4 4'/%3E%3C/svg%3E"); 144 | background-position: right 1rem center; 145 | background-repeat: no-repeat; 146 | background-size: 1rem; 147 | } 148 | 149 | [x-cloak] { 150 | @apply hidden; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require('tailwindcss/defaultTheme'); 2 | const colors = require('tailwindcss/colors'); 3 | 4 | module.exports = { 5 | content: ['./public/**/*.html', './src/**/*.{astro,js,jsx,svelte,ts,tsx,vue}'], 6 | theme: { 7 | extend: { 8 | fontFamily: { 9 | sans: ['Plus Jakarta Sans', ...defaultTheme.fontFamily.sans], 10 | }, 11 | colors: { 12 | current: 'currentColor', 13 | primary: colors.blue, 14 | success: colors.emerald, 15 | danger: colors.rose, 16 | warning: colors.amber, 17 | info: colors.indigo, 18 | }, 19 | }, 20 | }, 21 | plugins: [], 22 | }; 23 | --------------------------------------------------------------------------------