Compare commits

...

9 Commits

Author SHA1 Message Date
Ryukemeister 4b5e1636b4 Merge branch 'main' into app-card 2023-11-15 22:49:40 +05:30
Ryukemeister ee70029ef6 Merge branch 'main' into app-card 2023-11-15 14:40:15 +05:30
Ryukemeister 50167c3caf update view of app card 2023-11-15 14:36:04 +05:30
Ryukemeister 26a92bfa46 add card component(dumb) 2023-11-15 14:35:30 +05:30
Ryukemeister e91e772f8a button and badge components from shadcn 2023-11-15 14:34:48 +05:30
Ryukemeister 6a2c55a216 update packages 2023-11-15 14:33:50 +05:30
Ryukemeister 08b518db06 update typpings 2023-11-14 20:01:26 +05:30
Ryukemeister fc3a7c3549 shadcn setup 2023-11-14 14:59:38 +05:30
Ryukemeister a773f2630c init AppCard component 2023-11-09 12:30:11 +05:30
12 changed files with 366 additions and 3 deletions

View File

@ -0,0 +1,90 @@
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { useState, useEffect } from "react";
import { doesAppSupportTeamInstall } from "@calcom/app-store/utils";
import type { UserAdminTeams } from "@calcom/ee/teams/lib/getUserAdminTeams";
import type { AppFrontendPayload as App } from "@calcom/types/App";
import type { CredentialFrontendPayload as Credential } from "@calcom/types/Credential";
type CardProps = {
app: App;
credentials?: Credential[];
searchText?: string;
userAdminTeams?: UserAdminTeams;
};
export function Card({ app, credentials, searchText, userAdminTeams }: CardProps) {
const enabledOnTeams = doesAppSupportTeamInstall(app.categories, app.concurrentMeetings);
const appAdded = (credentials && credentials.length) || 0;
const appInstalled = enabledOnTeams && userAdminTeams ? userAdminTeams.length < appAdded : appAdded > 0;
const [searchTextIndex, setSearchTextIndex] = useState<number | undefined>(undefined);
useEffect(() => {
setSearchTextIndex(searchText ? app.name.toLowerCase().indexOf(searchText.toLowerCase()) : undefined);
}, [app.name, searchText]);
return (
<div className="border-subtle relative flex h-64 flex-col rounded-md border p-5">
<div className="flex">
<img
src={app.logo}
alt={`${app.name} Logo`}
className={cn(app.logo.includes("-dark") && "dark:invert", "mb-4 h-12 w-12 rounded-sm")}
/>
</div>
<div className="flex items-center">
<h3 className="text-emphasis font-medium">
{searchTextIndex != undefined && searchText ? (
<>
{app.name.substring(0, searchTextIndex)}
<span className="bg-yellow-300" data-testid="highlighted-text">
{app.name.substring(searchTextIndex, searchTextIndex + searchText.length)}
</span>
{app.name.substring(searchTextIndex + searchText.length)}
</>
) : (
app.name
)}
</h3>
</div>
<p
className="text-default mt-2 flex-grow text-sm"
style={{
overflow: "hidden",
display: "-webkit-box",
WebkitBoxOrient: "vertical",
WebkitLineClamp: "3",
}}>
{app.description}
</p>
<div className="mt-5 flex max-w-full flex-row justify-between gap-2">
<Button
color="secondary"
className="flex w-32 flex-grow justify-center"
data-testid={`app-store-app-card-${app.slug}`}>
<a href={`/apps/${app.slug}`}>Details</a>
</Button>
{}
</div>
<div className="max-w-44 absolute right-0 mr-4 flex flex-wrap justify-end gap-1">
{appInstalled ? (
<Badge variant="default" className="bg-green-200 text-white">
Installed
</Badge>
) : null}
{app.isTemplate && (
<span className="bg-error rounded-md px-2 py-1 text-sm font-normal text-red-800">Template</span>
)}
{(app.isDefault || (!app.isDefault && app.isGlobal)) && (
<span className="bg-subtle text-emphasis flex items-center rounded-md px-2 py-1 text-sm font-normal">
Default
</span>
)}
</div>
</div>
);
}

View File

@ -0,0 +1,2 @@
export { AppCard } from "./index";
export * from "../types";

View File

@ -0,0 +1,10 @@
import { Card } from "app-card/components/Card";
type AppCardProps = {
appName: string;
};
export function AppCard({ appName }: AppCardProps) {
// TODO: fetch app based on appName prop and pass it down to dumb Card component
return <Card />;
}

View File

@ -0,0 +1,16 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.cjs",
"css": "globals.css",
"baseColor": "slate",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}

View File

@ -7,4 +7,80 @@
@tailwind components;
@tailwind utilities;
@import "../ui/styles/shared-globals.css";
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

View File

@ -11,6 +11,7 @@
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^15.0.1",
"@types/node": "^20.9.0",
"@types/react": "18.0.26",
"@types/react-dom": "^18.0.9",
"@vitejs/plugin-react": "^2.2.0",
@ -18,5 +19,13 @@
"typescript": "^4.9.4",
"vite": "^4.1.2"
},
"main": "./index"
"main": "./index",
"dependencies": {
"@radix-ui/react-slot": "^1.0.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"lucide-react": "^0.292.0",
"tailwind-merge": "^2.0.0",
"tailwindcss-animate": "^1.0.7"
}
}

View File

@ -0,0 +1,30 @@
import { cn } from "@/lib/utils";
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
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",
{
variants: {
variant: {
default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
);
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
}
export { Badge, badgeVariants };

View File

@ -0,0 +1,46 @@
import { cn } from "@/lib/utils";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
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",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
}
);
Button.displayName = "Button";
export { Button, buttonVariants };

View File

@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

View File

@ -3,5 +3,78 @@ const base = require("@calcom/config/tailwind-preset");
/** @type {import('tailwindcss').Config} */
module.exports = {
...base,
content: ["./bookings/**/*.tsx"],
darkMode: ["class"],
content: [
"./pages/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx}",
"./bookings/**/*.tsx",
],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: 0 },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
};

View File

@ -3,7 +3,8 @@
"compilerOptions": {
"baseUrl": ".",
"paths": {
"~/*": ["/*"]
"~/*": ["/*"],
"@/*": ["./src/*"]
},
"resolveJsonModule": true
},

View File

@ -1,7 +1,10 @@
import react from "@vitejs/plugin-react";
import path from "path";
import { resolve } from "path";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [react()],
build: {
lib: {
entry: [resolve(__dirname, "booker/export.ts")],
@ -23,6 +26,7 @@ export default defineConfig({
fs: resolve("../../node_modules/rollup-plugin-node-builtins"),
path: resolve("../../node_modules/rollup-plugin-node-builtins"),
os: resolve("../../node_modules/rollup-plugin-node-builtins"),
"@": path.resolve(__dirname, "./src"),
},
},
});