/integrations facelift (#858)
This commit is contained in:
1 parent
7dc4a55319
commit
c3dc18643e
32 files changed
+1259
-716
No files matched your search
+11
-5
@@ -1,7 +1,7 @@
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import React from "react";
|
||||
import React, { ReactNode } from "react";
|
||||
|
||||
type DialogProps = React.ComponentProps<typeof DialogPrimitive["Root"]>;
|
||||
export type DialogProps = React.ComponentProps<typeof DialogPrimitive["Root"]>;
|
||||
export function Dialog(props: DialogProps) {
|
||||
const { children, ...other } = props;
|
||||
return (
|
||||
@@ -35,9 +35,15 @@ export function DialogHeader({ title, subtitle }: DialogHeaderProps) {
|
||||
<h3 className="font-cal text-gray-900 text-lg font-bold leading-6" id="modal-title">
|
||||
{title}
|
||||
</h3>
|
||||
<div>
|
||||
<p className="text-gray-400 text-sm">{subtitle}</p>
|
||||
</div>
|
||||
<div className="text-gray-400 text-sm">{subtitle}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function DialogFooter(props: { children: ReactNode }) {
|
||||
return (
|
||||
<div>
|
||||
<div className="mt-5 flex space-x-2 justify-end">{props.children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import Link from "next/link";
|
||||
import { createElement } from "react";
|
||||
|
||||
import classNames from "@lib/classNames";
|
||||
|
||||
export function List(props: JSX.IntrinsicElements["ul"]) {
|
||||
return (
|
||||
<ul {...props} className={classNames("overflow-hidden rounded-sm sm:mx-0", props.className)}>
|
||||
{props.children}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
export type ListItemProps = { expanded?: boolean } & ({ href?: never } & JSX.IntrinsicElements["li"]);
|
||||
|
||||
export function ListItem(props: ListItemProps) {
|
||||
const { href, expanded, ...passThroughProps } = props;
|
||||
|
||||
const elementType = href ? "a" : "li";
|
||||
|
||||
const element = createElement(
|
||||
elementType,
|
||||
{
|
||||
...passThroughProps,
|
||||
className: classNames(
|
||||
"items-center bg-white min-w-0 flex-1 flex border-gray-200",
|
||||
expanded ? "my-2 border" : "border -mb-px last:mb-0",
|
||||
props.className,
|
||||
(props.onClick || href) && "hover:bg-neutral-50"
|
||||
),
|
||||
},
|
||||
props.children
|
||||
);
|
||||
|
||||
return href ? (
|
||||
<Link passHref href={href}>
|
||||
{element}
|
||||
</Link>
|
||||
) : (
|
||||
element
|
||||
);
|
||||
}
|
||||
|
||||
export function ListItemTitle<TComponent extends keyof JSX.IntrinsicElements = "span">(
|
||||
props: JSX.IntrinsicElements[TComponent] & { component?: TComponent }
|
||||
) {
|
||||
const { component = "span", ...passThroughProps } = props;
|
||||
|
||||
return createElement(
|
||||
component,
|
||||
{
|
||||
...passThroughProps,
|
||||
className: classNames("text-sm font-medium text-neutral-900 truncate", props.className),
|
||||
},
|
||||
props.children
|
||||
);
|
||||
}
|
||||
|
||||
export function ListItemText<TComponent extends keyof JSX.IntrinsicElements = "span">(
|
||||
props: JSX.IntrinsicElements[TComponent] & { component?: TComponent }
|
||||
) {
|
||||
const { component = "span", ...passThroughProps } = props;
|
||||
|
||||
return createElement(
|
||||
component,
|
||||
{
|
||||
...passThroughProps,
|
||||
className: classNames("text-sm text-gray-500", props.className),
|
||||
},
|
||||
props.children
|
||||
);
|
||||
}
|
||||
+31
-6
@@ -19,6 +19,7 @@ import LicenseBanner from "@ee/components/LicenseBanner";
|
||||
import HelpMenuItemDynamic from "@ee/lib/intercom/HelpMenuItemDynamic";
|
||||
|
||||
import classNames from "@lib/classNames";
|
||||
import { shouldShowOnboarding } from "@lib/getting-started";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
|
||||
import { trpc } from "@lib/trpc";
|
||||
|
||||
@@ -29,10 +30,7 @@ import Logo from "./Logo";
|
||||
|
||||
function useMeQuery() {
|
||||
const [session] = useSession();
|
||||
const meQuery = trpc.useQuery(["viewer.me"], {
|
||||
// refetch max once per 5s
|
||||
staleTime: 5000,
|
||||
});
|
||||
const meQuery = trpc.useQuery(["viewer.me"]);
|
||||
|
||||
useEffect(() => {
|
||||
// refetch if sesion changes
|
||||
@@ -59,6 +57,26 @@ function useRedirectToLoginIfUnauthenticated() {
|
||||
}, [loading, session, router]);
|
||||
}
|
||||
|
||||
export function ShellSubHeading(props: {
|
||||
title: ReactNode;
|
||||
subtitle?: ReactNode;
|
||||
actions?: ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={classNames("block sm:flex justify-between mb-3", props.className)}>
|
||||
<div>
|
||||
{/* TODO should be Roboto */}
|
||||
<h2 className="text-lg font-bold text-gray-900 flex items-center content-center space-x-2">
|
||||
{props.title}
|
||||
</h2>
|
||||
{props.subtitle && <p className="text-sm text-neutral-500 mr-4">{props.subtitle}</p>}
|
||||
</div>
|
||||
{props.actions && <div className="mb-4 flex-shrink-0">{props.actions}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Shell(props: {
|
||||
centered?: boolean;
|
||||
title?: string;
|
||||
@@ -74,6 +92,15 @@ export default function Shell(props: {
|
||||
const telemetry = useTelemetry();
|
||||
const query = useMeQuery();
|
||||
|
||||
useEffect(
|
||||
function redirectToOnboardingIfNeeded() {
|
||||
if (query.data && shouldShowOnboarding(query.data)) {
|
||||
router.push("/getting-started");
|
||||
}
|
||||
},
|
||||
[query.data, router]
|
||||
);
|
||||
|
||||
const navigation = [
|
||||
{
|
||||
name: "Event Types",
|
||||
@@ -209,7 +236,6 @@ export default function Shell(props: {
|
||||
<div className="mb-4 flex-shrink-0">{props.CTA}</div>
|
||||
</div>
|
||||
<div className="px-4 sm:px-6 md:px-8">{props.children}</div>
|
||||
|
||||
{/* show bottom navigation for md and smaller (tablet and phones) */}
|
||||
<nav className="bottom-nav md:hidden flex fixed bottom-0 bg-white w-full shadow">
|
||||
{/* note(PeerRich): using flatMap instead of map to remove settings from bottom nav */}
|
||||
@@ -239,7 +265,6 @@ export default function Shell(props: {
|
||||
)
|
||||
)}
|
||||
</nav>
|
||||
|
||||
{/* add padding to content for mobile navigation*/}
|
||||
<div className="block md:hidden pt-12" />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { useId } from "@radix-ui/react-id";
|
||||
import { forwardRef, ReactNode } from "react";
|
||||
import { FormProvider, UseFormReturn } from "react-hook-form";
|
||||
|
||||
import classNames from "@lib/classNames";
|
||||
|
||||
export const Input = forwardRef<HTMLInputElement, JSX.IntrinsicElements["input"]>(function Input(props, ref) {
|
||||
return (
|
||||
<input
|
||||
{...props}
|
||||
ref={ref}
|
||||
className={classNames(
|
||||
"mt-1 block w-full border border-gray-300 rounded-sm shadow-sm py-2 px-3 focus:outline-none focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm",
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export function Label(props: JSX.IntrinsicElements["label"]) {
|
||||
return (
|
||||
<label {...props} className={classNames("block text-sm font-medium text-gray-700", props.className)}>
|
||||
{props.children}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
export const TextField = forwardRef<
|
||||
HTMLInputElement,
|
||||
{
|
||||
label: ReactNode;
|
||||
} & React.ComponentProps<typeof Input> & {
|
||||
labelProps?: React.ComponentProps<typeof Label>;
|
||||
}
|
||||
>(function TextField(props, ref) {
|
||||
const id = useId();
|
||||
const { label, ...passThroughToInput } = props;
|
||||
|
||||
// TODO: use `useForm()` from RHF and get error state here too!
|
||||
return (
|
||||
<div>
|
||||
<Label htmlFor={id} {...props.labelProps}>
|
||||
{label}
|
||||
</Label>
|
||||
<Input id={id} {...passThroughToInput} ref={ref} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const Form = forwardRef<HTMLFormElement, { form: UseFormReturn<any> } & JSX.IntrinsicElements["form"]>(
|
||||
function Form(props, ref) {
|
||||
const { form, ...passThrough } = props;
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<form ref={ref} {...passThrough}>
|
||||
{props.children}
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
);
|
||||
Whitespace-only changes.
@@ -5,6 +5,7 @@ import { ReactNode } from "react";
|
||||
export interface AlertProps {
|
||||
title?: ReactNode;
|
||||
message?: ReactNode;
|
||||
actions?: ReactNode;
|
||||
className?: string;
|
||||
severity: "success" | "warning" | "error";
|
||||
}
|
||||
@@ -36,6 +37,7 @@ export function Alert(props: AlertProps) {
|
||||
<h3 className="text-sm font-medium">{props.title}</h3>
|
||||
<div className="text-sm">{props.message}</div>
|
||||
</div>
|
||||
{props.actions && <div className="text-sm">{props.actions}</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -13,7 +13,7 @@ export const Badge = function Badge(props: BadgeProps) {
|
||||
<span
|
||||
{...passThroughProps}
|
||||
className={classNames(
|
||||
"font-bold px-2 py-0.5 inline-block rounded-sm",
|
||||
"font-bold px-2 py-0.5 inline-block rounded-sm text-xs",
|
||||
variant === "default" && "bg-yellow-100 text-yellow-800",
|
||||
variant === "success" && "bg-green-100 text-green-800",
|
||||
variant === "gray" && "bg-gray-200 text-gray-800",
|
||||
|
||||
@@ -4,7 +4,7 @@ import React, { forwardRef } from "react";
|
||||
import classNames from "@lib/classNames";
|
||||
import { SVGComponent } from "@lib/types/SVGComponent";
|
||||
|
||||
export type ButtonProps = {
|
||||
export type ButtonBaseProps = {
|
||||
color?: "primary" | "secondary" | "minimal" | "warn";
|
||||
size?: "base" | "sm" | "lg" | "fab" | "icon";
|
||||
loading?: boolean;
|
||||
@@ -13,10 +13,12 @@ export type ButtonProps = {
|
||||
StartIcon?: SVGComponent;
|
||||
EndIcon?: SVGComponent;
|
||||
shallow?: boolean;
|
||||
} & (
|
||||
| (Omit<JSX.IntrinsicElements["a"], "href"> & { href: LinkProps["href"] })
|
||||
| (JSX.IntrinsicElements["button"] & { href?: never })
|
||||
);
|
||||
};
|
||||
export type ButtonProps = ButtonBaseProps &
|
||||
(
|
||||
| (Omit<JSX.IntrinsicElements["a"], "href"> & { href: LinkProps["href"] })
|
||||
| (JSX.IntrinsicElements["button"] & { href?: never })
|
||||
);
|
||||
|
||||
export const Button = forwardRef<HTMLAnchorElement | HTMLButtonElement, ButtonProps>(function Button(
|
||||
props: ButtonProps,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useId } from "@radix-ui/react-id";
|
||||
import * as Label from "@radix-ui/react-label";
|
||||
import * as PrimitiveSwitch from "@radix-ui/react-switch";
|
||||
import { useState } from "react";
|
||||
@@ -16,7 +17,7 @@ export default function Switch(props) {
|
||||
}
|
||||
setChecked(change);
|
||||
};
|
||||
|
||||
const id = useId();
|
||||
return (
|
||||
<div className="flex items-center h-[20px]">
|
||||
<PrimitiveSwitch.Root
|
||||
@@ -25,6 +26,7 @@ export default function Switch(props) {
|
||||
onCheckedChange={onPrimitiveCheckedChange}
|
||||
{...primitiveProps}>
|
||||
<PrimitiveSwitch.Thumb
|
||||
id={id}
|
||||
className={classNames(
|
||||
"bg-white w-[16px] h-[16px] block transition-transform",
|
||||
checked ? "translate-x-[16px]" : "translate-x-0"
|
||||
@@ -32,7 +34,9 @@ export default function Switch(props) {
|
||||
/>
|
||||
</PrimitiveSwitch.Root>
|
||||
{label && (
|
||||
<Label.Root className="text-neutral-700 text-sm align-text-top ml-3 font-medium cursor-pointer">
|
||||
<Label.Root
|
||||
htmlFor={id}
|
||||
className="text-neutral-700 text-sm align-text-top ml-3 font-medium cursor-pointer">
|
||||
{label}
|
||||
</Label.Root>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user