Website public beta (#2418)
This is a feature branch containing the progress for the website URL: https://tspwebsitepr.z22.web.core.windows.net/prs/2418/
|
@ -2,6 +2,7 @@
|
|||
**/dist-dev/
|
||||
**/test/output/
|
||||
**/bin/
|
||||
**/out/
|
||||
**/obj/
|
||||
**/.vs/
|
||||
**/.docusaurus/
|
||||
|
@ -48,3 +49,7 @@ packages/compiler/templates/__snapshots__/
|
|||
|
||||
#.tsp init template
|
||||
eng/feeds/
|
||||
|
||||
# Skip formatting tsp files
|
||||
|
||||
*.noformat.tsp
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@typespec/playground",
|
||||
"comment": "",
|
||||
"type": "none"
|
||||
}
|
||||
],
|
||||
"packageName": "@typespec/playground"
|
||||
}
|
|
@ -670,7 +670,7 @@ importers:
|
|||
specifier: ~9.42.0
|
||||
version: 9.42.0(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)(scheduler@0.20.2)
|
||||
'@fluentui/react-icons':
|
||||
specifier: ~2.0.217
|
||||
specifier: ^2.0.221
|
||||
version: 2.0.224(react@18.2.0)
|
||||
'@typespec/bundler':
|
||||
specifier: workspace:~0.1.0-alpha.4
|
||||
|
@ -700,7 +700,7 @@ importers:
|
|||
specifier: workspace:~0.51.0
|
||||
version: link:../versioning
|
||||
clsx:
|
||||
specifier: ~2.0.0
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
debounce:
|
||||
specifier: ~2.0.0
|
||||
|
@ -1287,6 +1287,9 @@ importers:
|
|||
'@docusaurus/preset-classic':
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.1(@algolia/client-search@4.22.0)(@swc/core@1.3.101)(@types/react@18.2.45)(eslint@8.56.0)(react-dom@18.2.0)(react@18.2.0)(search-insights@2.13.0)(typescript@5.3.3)
|
||||
'@docusaurus/theme-classic':
|
||||
specifier: ~3.0.0
|
||||
version: 3.0.1(@swc/core@1.3.101)(@types/react@18.2.45)(eslint@8.56.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3)
|
||||
'@docusaurus/theme-common':
|
||||
specifier: ~3.0.0
|
||||
version: 3.0.1(@docusaurus/types@3.0.1)(@swc/core@1.3.101)(eslint@8.56.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3)
|
||||
|
@ -1296,12 +1299,18 @@ importers:
|
|||
'@fluentui/react-components':
|
||||
specifier: ~9.42.0
|
||||
version: 9.42.0(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0)(scheduler@0.20.2)
|
||||
'@fluentui/react-icons':
|
||||
specifier: ^2.0.221
|
||||
version: 2.0.224(react@18.2.0)
|
||||
'@mdx-js/react':
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0(@types/react@18.2.45)(react@18.2.0)
|
||||
'@typespec/playground':
|
||||
specifier: workspace:~0.1.0-alpha.4
|
||||
version: link:../playground
|
||||
clsx:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
es-module-shims:
|
||||
specifier: ~1.8.0
|
||||
version: 1.8.2
|
||||
|
@ -6672,6 +6681,7 @@ packages:
|
|||
|
||||
/@swagger-api/apidom-ns-json-schema-draft-4@0.89.0:
|
||||
resolution: {integrity: sha512-7gXy3BPLkS7p7dmz9Hbf7ia4lH0NAaW2i7GcQdpX48pAUTR0/7Y+BPd38sgRxIOpebReWxnoAcKAfkak/KCQ3A==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@babel/runtime-corejs3': 7.23.6
|
||||
'@swagger-api/apidom-ast': 0.89.0
|
||||
|
|
|
@ -40,6 +40,7 @@ words:
|
|||
- multis
|
||||
- munge
|
||||
- mylib
|
||||
- noformat
|
||||
- noopener
|
||||
- noreferrer
|
||||
- nostdlib
|
||||
|
@ -80,6 +81,7 @@ words:
|
|||
- vsix
|
||||
- vswhere
|
||||
- westus
|
||||
- xlarge
|
||||
- xplat
|
||||
ignorePaths:
|
||||
- "**/node_modules/**"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
id: usage
|
||||
title: Usage
|
||||
id: cli
|
||||
title: Cli usage
|
||||
---
|
||||
|
||||
# Usage
|
|
@ -1,16 +0,0 @@
|
|||
---
|
||||
id: introduction
|
||||
title: Introduction
|
||||
slug: /
|
||||
---
|
||||
|
||||
# Introduction to TypeSpec
|
||||
|
||||
TypeSpec is a language for describing cloud service APIs and generating other API description languages, client and service code, documentation, and other assets. TypeSpec provides highly extensible core language primitives that can describe API shapes common among REST, GraphQL, gRPC, and other protocols.
|
||||
|
||||
## Try TypeSpec
|
||||
|
||||
You can try TypeSpec on the web without installing anything.
|
||||
|
||||
- [TypeSpec playground](https://cadlplayground.z22.web.core.windows.net)
|
||||
- [TypeSpec playground for Azure services](https://azure.github.io/typespec-azure/playground)
|
|
@ -30,7 +30,7 @@ scalar uuid extends string;
|
|||
|
||||
## Emitter options interpolation and standardization
|
||||
|
||||
Emitter options are not able to interpolate other values. See [details](../introduction/configuration/configuration.md#variable-interpolation)
|
||||
Emitter options are not able to interpolate other values. See [details](../handbook/configuration/configuration.md#variable-interpolation)
|
||||
|
||||
```yaml
|
||||
output-dir: {cwd}/generated
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@fluentui/react-components": "~9.42.0",
|
||||
"@fluentui/react-icons": "~2.0.217",
|
||||
"@fluentui/react-icons": "^2.0.221",
|
||||
"@typespec/bundler": "workspace:~0.1.0-alpha.4",
|
||||
"@typespec/compiler": "workspace:~0.51.0",
|
||||
"@typespec/html-program-viewer": "workspace:~0.51.0",
|
||||
|
@ -88,7 +88,7 @@
|
|||
"swagger-ui-react": "~5.10.3",
|
||||
"vscode-languageserver-textdocument": "~1.0.8",
|
||||
"vscode-languageserver": "~9.0.0",
|
||||
"clsx": "~2.0.0"
|
||||
"clsx": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.20",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export { createBrowserHost } from "./browser-host.js";
|
||||
export { registerMonacoDefaultWorkersForVite } from "./monaco-worker.js";
|
||||
export { registerMonacoLanguage } from "./services.js";
|
||||
export { StateStorage, createUrlStateStorage } from "./state-storage.js";
|
||||
export { StateStorage, UrlStateStorage, createUrlStateStorage } from "./state-storage.js";
|
||||
export type { BrowserHost, PlaygroundSample } from "./types.js";
|
||||
|
|
|
@ -14,6 +14,7 @@ export type { PlaygroundProps, PlaygroundSaveData } from "./playground.js";
|
|||
export {
|
||||
StandalonePlayground,
|
||||
createReactPlayground,
|
||||
createStandalonePlaygroundStateStorage,
|
||||
renderReactPlayground,
|
||||
} from "./standalone.js";
|
||||
export type * from "./types.js";
|
||||
|
|
|
@ -20,7 +20,7 @@ import { createRoot } from "react-dom/client";
|
|||
import { createBrowserHost } from "../browser-host.js";
|
||||
import { LibraryImportOptions } from "../core.js";
|
||||
import { registerMonacoLanguage } from "../services.js";
|
||||
import { StateStorage, createUrlStateStorage } from "../state-storage.js";
|
||||
import { StateStorage, UrlStateStorage, createUrlStateStorage } from "../state-storage.js";
|
||||
import { BrowserHost } from "../types.js";
|
||||
import { Playground, PlaygroundProps, PlaygroundSaveData } from "./playground.js";
|
||||
|
||||
|
@ -122,7 +122,7 @@ export async function renderReactPlayground(config: ReactPlaygroundConfig) {
|
|||
);
|
||||
}
|
||||
|
||||
export function createStandalonePlaygroundStateStorage(): StateStorage<PlaygroundSaveData> {
|
||||
export function createStandalonePlaygroundStateStorage(): UrlStateStorage<PlaygroundSaveData> {
|
||||
const stateStorage = createUrlStateStorage<PlaygroundSaveData>({
|
||||
content: {
|
||||
queryParam: "c",
|
||||
|
@ -142,6 +142,7 @@ export function createStandalonePlaygroundStateStorage(): StateStorage<Playgroun
|
|||
|
||||
return {
|
||||
load: stateStorage.load,
|
||||
resolveSearchParams: stateStorage.resolveSearchParams,
|
||||
save(data: PlaygroundSaveData) {
|
||||
stateStorage.save(
|
||||
data.sampleName ? { sampleName: data.sampleName, options: data.options } : data
|
||||
|
|
|
@ -5,6 +5,10 @@ export interface StateStorage<T extends object> {
|
|||
save(t: Partial<T>): void;
|
||||
}
|
||||
|
||||
export interface UrlStateStorage<T extends object> extends StateStorage<T> {
|
||||
resolveSearchParams(t: Partial<T>): URLSearchParams;
|
||||
}
|
||||
|
||||
export type UrlStorageSchema<T> = {
|
||||
[key in keyof T]: UrlStorageItem;
|
||||
};
|
||||
|
@ -26,8 +30,8 @@ export interface UrlStorageItem {
|
|||
*/
|
||||
export function createUrlStateStorage<const T extends object>(
|
||||
schema: UrlStorageSchema<T>
|
||||
): StateStorage<T> {
|
||||
return { load, save };
|
||||
): UrlStateStorage<T> {
|
||||
return { load, save, resolveSearchParams };
|
||||
|
||||
function load(): Partial<T> {
|
||||
const result: Record<string, string> = {};
|
||||
|
@ -78,7 +82,12 @@ export function createUrlStateStorage<const T extends object>(
|
|||
}
|
||||
|
||||
function save(data: T) {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const params = resolveSearchParams(data, true);
|
||||
history.pushState(null, "", window.location.pathname + "?" + params.toString());
|
||||
}
|
||||
|
||||
function resolveSearchParams(data: T, mergeWithExisting = false): URLSearchParams {
|
||||
const params = new URLSearchParams(mergeWithExisting ? location.search : undefined);
|
||||
for (const [key, item] of Object.entries<UrlStorageItem>(schema)) {
|
||||
const value = (data as any)[key];
|
||||
|
||||
|
@ -90,7 +99,7 @@ export function createUrlStateStorage<const T extends object>(
|
|||
params.delete(item.queryParam);
|
||||
}
|
||||
}
|
||||
history.pushState(null, "", window.location.pathname + "?" + params.toString());
|
||||
return params;
|
||||
}
|
||||
|
||||
function compress(item: UrlStorageItem, value: string): string {
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
declare module "*.png";
|
||||
declare module "*.json";
|
||||
declare module "!!raw-loader!@site/static/*" {
|
||||
const contents: string;
|
||||
export = contents;
|
||||
}
|
|
@ -97,7 +97,6 @@ const config: Config = {
|
|||
/** @type {import('@docusaurus/preset-classic').Options} */
|
||||
{
|
||||
docs: {
|
||||
routeBasePath: "/",
|
||||
sidebarPath: require.resolve("./sidebars.js"),
|
||||
path: "../../docs",
|
||||
versions: getVersionLabels(),
|
||||
|
@ -113,7 +112,7 @@ const config: Config = {
|
|||
],
|
||||
],
|
||||
staticDirectories: [
|
||||
resolve(__dirname, "./node_modules/@typespec/spec/dist"),
|
||||
resolve(__dirname, "static"),
|
||||
resolve(__dirname, "./node_modules/es-module-shims/dist"),
|
||||
],
|
||||
|
||||
|
@ -182,18 +181,39 @@ const config: Config = {
|
|||
navbar: {
|
||||
title: "TypeSpec",
|
||||
items: [
|
||||
{
|
||||
type: "dropdown",
|
||||
label: "Use cases",
|
||||
items: [
|
||||
{
|
||||
label: "OpenAPI",
|
||||
to: "/openapi",
|
||||
},
|
||||
{
|
||||
label: "Data validation and type consistency",
|
||||
to: "/data-validation",
|
||||
},
|
||||
{
|
||||
label: "Tooling support",
|
||||
to: "/tooling",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "doc",
|
||||
docId: "introduction/introduction",
|
||||
docId: "introduction/installation",
|
||||
position: "left",
|
||||
label: "Docs",
|
||||
},
|
||||
{
|
||||
to: "/specification",
|
||||
position: "left",
|
||||
label: "Specification",
|
||||
},
|
||||
{ to: "/playground", label: "Playground", position: "left" },
|
||||
{
|
||||
label: "Community",
|
||||
to: "/community",
|
||||
},
|
||||
{
|
||||
label: "Getting started",
|
||||
to: "/docs/introduction/installation",
|
||||
},
|
||||
{
|
||||
type: "docsVersionDropdown",
|
||||
position: "right",
|
||||
|
@ -208,40 +228,27 @@ const config: Config = {
|
|||
},
|
||||
footer: {
|
||||
style: "dark",
|
||||
// links: [
|
||||
// {
|
||||
// title: "Docs",
|
||||
// items: [
|
||||
// {
|
||||
// label: "Introduction",
|
||||
// to: "/",
|
||||
// },
|
||||
// {
|
||||
// label: "Language basics",
|
||||
// to: "/language-basics/overview",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// title: "Community & Support",
|
||||
// items: [
|
||||
// {
|
||||
// label: "Stack Overflow",
|
||||
// href: "https://stackoverflow.microsoft.com/search?q=typespec",
|
||||
// },
|
||||
// {
|
||||
// label: "Microsoft Teams Channel",
|
||||
// href: "http://aka.ms/typespec/discussions",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// ],
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Microsoft Corp.`,
|
||||
links: [
|
||||
{
|
||||
title: "Docs",
|
||||
items: [
|
||||
{
|
||||
label: "Introduction",
|
||||
to: "/docs",
|
||||
},
|
||||
{
|
||||
label: "Language basics",
|
||||
to: "/docs/language-basics/overview",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
copyright: `© ${new Date().getFullYear()} Microsoft`,
|
||||
},
|
||||
prism: {
|
||||
theme: themes.oneLight,
|
||||
darkTheme: themes.oneDark,
|
||||
additionalLanguages: ["http"],
|
||||
additionalLanguages: ["http", "shell-session", "protobuf"],
|
||||
},
|
||||
mermaid: {},
|
||||
algolia: {
|
||||
|
|
|
@ -25,18 +25,21 @@
|
|||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^3.0.0",
|
||||
"@docusaurus/preset-classic": "^3.0.0",
|
||||
"@docusaurus/theme-mermaid": "^3.0.0",
|
||||
"@docusaurus/plugin-content-docs": "~3.0.0",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"prism-react-renderer": "^2.1.0",
|
||||
"react": "~18.2.0",
|
||||
"react-dom": "~18.2.0",
|
||||
"@typespec/playground": "workspace:~0.1.0-alpha.4",
|
||||
"@fluentui/react-components": "~9.42.0",
|
||||
"es-module-shims": "~1.8.0",
|
||||
"@docusaurus/preset-classic": "^3.0.0",
|
||||
"@docusaurus/theme-classic": "~3.0.0",
|
||||
"@docusaurus/theme-common": "~3.0.0",
|
||||
"prismjs": "~1.29.0"
|
||||
"@docusaurus/theme-mermaid": "^3.0.0",
|
||||
"@fluentui/react-components": "~9.42.0",
|
||||
"@fluentui/react-icons": "^2.0.221",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"@typespec/playground": "workspace:~0.1.0-alpha.4",
|
||||
"clsx": "^2.0.0",
|
||||
"es-module-shims": "~1.8.0",
|
||||
"prism-react-renderer": "^2.1.0",
|
||||
"prismjs": "~1.29.0",
|
||||
"react-dom": "~18.2.0",
|
||||
"react": "~18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@swc/core": "^1.3.62",
|
||||
|
|
|
@ -39,26 +39,14 @@ const sidebars: SidebarsConfig = {
|
|||
docsSidebar: [
|
||||
{
|
||||
type: "category",
|
||||
label: "Introduction",
|
||||
label: "Getting started",
|
||||
items: [
|
||||
"introduction/introduction",
|
||||
"introduction/installation",
|
||||
{
|
||||
type: "category",
|
||||
label: "Editor",
|
||||
items: ["introduction/editor/vscode", "introduction/editor/vs"],
|
||||
},
|
||||
"introduction/usage",
|
||||
"introduction/style-guide",
|
||||
"introduction/formatter",
|
||||
"introduction/reproducibility",
|
||||
{
|
||||
type: "category",
|
||||
label: "Configuration",
|
||||
items: ["introduction/configuration/configuration", "introduction/configuration/tracing"],
|
||||
},
|
||||
"introduction/releases",
|
||||
"introduction/faq",
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -70,6 +58,23 @@ const sidebars: SidebarsConfig = {
|
|||
"getting-started/typespec-for-openapi-dev",
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Handbook",
|
||||
items: [
|
||||
"handbook/cli",
|
||||
"handbook/style-guide",
|
||||
"handbook/formatter",
|
||||
"handbook/reproducibility",
|
||||
{
|
||||
type: "category",
|
||||
label: "Configuration",
|
||||
items: ["handbook/configuration/configuration", "handbook/configuration/tracing"],
|
||||
},
|
||||
"handbook/releases",
|
||||
"handbook/faq",
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "📐 Language Basics",
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
.img {
|
||||
display: block;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import useBaseUrl from "@docusaurus/useBaseUrl";
|
||||
import style from "./asset-img.module.css";
|
||||
|
||||
export interface AssetImgProps {
|
||||
src: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component for rendering an image resolving the relative path.
|
||||
*/
|
||||
export const AssetImg = ({ src, ...props }: AssetImgProps) => {
|
||||
const fullSrc = useBaseUrl(`/img/${src}`);
|
||||
return <img className={style["img"]} src={fullSrc} {...props} />;
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
.button {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
text-decoration-line: none;
|
||||
vertical-align: middle;
|
||||
margin: 0px;
|
||||
overflow: hidden;
|
||||
background-color: var(--colorNeutralBackground1);
|
||||
color: var(--colorNeutralForeground1);
|
||||
border: var(--strokeWidthThin) solid var(--colorNeutralStroke1);
|
||||
font-family: var(--fontFamilyBase);
|
||||
outline-style: none;
|
||||
padding: 5px var(--spacingHorizontalM);
|
||||
min-width: 96px;
|
||||
border-radius: var(--borderRadiusMedium);
|
||||
font-size: var(--fontSizeBase300);
|
||||
font-weight: var(--fontWeightSemibold);
|
||||
line-height: var(--lineHeightBase300);
|
||||
transition-duration: var(--durationFaster);
|
||||
transition-property: background, border, color;
|
||||
transition-timing-function: var(--curveEasyEase);
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
color: var(--colorNeutralForeground1);
|
||||
}
|
||||
|
||||
.appearance-primary {
|
||||
color: var(--colorNeutralForegroundOnBrand);
|
||||
background-color: var(--colorBrandBackground);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.appearance-primary:hover {
|
||||
color: var(--colorNeutralForegroundOnBrand);
|
||||
}
|
||||
|
||||
.appearance-outline {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.appearance-outline:hover {
|
||||
border-color: var(--colorNeutralStroke1Hover);
|
||||
text-decoration: none;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import clsx from "clsx";
|
||||
import { ReactNode } from "react";
|
||||
import { Link } from "../link/link";
|
||||
import style from "./button.module.css";
|
||||
|
||||
export interface ButtonProps {
|
||||
appearance?: "primary" | "outline";
|
||||
as?: "a";
|
||||
href: string;
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Button = ({ href, children, className, appearance }: ButtonProps) => {
|
||||
const appearanceCls = style[`appearance-${appearance ?? "primary"}`];
|
||||
return (
|
||||
<Link className={clsx(style["button"], appearanceCls, className)} href={href}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
.card {
|
||||
border-radius: 22px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bg {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 22px;
|
||||
background-color: var(--colorNeutralBackground1);
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.no-padding .content {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.blend > .bg {
|
||||
opacity: 0.2;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import clsx from "clsx";
|
||||
import style from "./card.module.css";
|
||||
|
||||
export interface CardProps {
|
||||
/** Should skip padding. */
|
||||
noPadding?: boolean;
|
||||
/** Should the background blend. */
|
||||
blend?: boolean;
|
||||
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Card = ({ children, className, noPadding, blend }: CardProps) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
style["card"],
|
||||
noPadding && style["no-padding"],
|
||||
blend && style["blend"],
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className={style["bg"]}></div>
|
||||
<div className={style["content"]}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
.code-block {
|
||||
margin: 0;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import CodeBlockDocusaurus, { Props } from "@theme/CodeBlock";
|
||||
import clsx from "clsx";
|
||||
import style from "./code-block.module.css";
|
||||
|
||||
export interface CodeBlockProps extends Props {}
|
||||
export const CodeBlock = (props: CodeBlockProps) => {
|
||||
return <CodeBlockDocusaurus {...props} className={clsx(style["code-block"], props.className)} />;
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
.items-list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.item-image {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
max-width: 64px;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
import clsx from "clsx";
|
||||
import { FluentImageName, FluentImg } from "../fluent-img";
|
||||
import { Link } from "../link/link";
|
||||
import { DescriptionText, NeutralText, Text } from "../text/text";
|
||||
import style from "./feature-list.module.css";
|
||||
|
||||
interface FeatureListItem {
|
||||
title: string;
|
||||
description?: string;
|
||||
image?: FluentImageName;
|
||||
link?: string;
|
||||
}
|
||||
|
||||
export interface FeatureListProps {
|
||||
items?: FeatureListItem[];
|
||||
}
|
||||
|
||||
export const FeatureList = ({ items }: FeatureListProps) => {
|
||||
if (items === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const content = items.map((x, i) => <FeatureListItemEl key={i} {...x} />);
|
||||
return <div className={clsx(style["items-list"])}>{content}</div>;
|
||||
};
|
||||
|
||||
interface FeatureListItemElProps extends FeatureListItem {}
|
||||
|
||||
const FeatureListItemEl = ({ title, description, image, link }: FeatureListItemElProps) => {
|
||||
return (
|
||||
<div className={style["item"]}>
|
||||
{image && <FluentImg className={style["item-image"]} name={image} />}
|
||||
<div className={style["item-content"]}>
|
||||
<NeutralText size={"large"}>{title}</NeutralText>
|
||||
<DescriptionText>{description}</DescriptionText>
|
||||
{link && (
|
||||
<Link href={link}>
|
||||
<Text>Learn more →</Text>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
import { useColorMode } from "@docusaurus/theme-common";
|
||||
import useBaseUrl from "@docusaurus/useBaseUrl";
|
||||
|
||||
export interface FluentImgProps {
|
||||
name: FluentImageName;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export type FluentImageName =
|
||||
| "book-pencil"
|
||||
| "chat"
|
||||
| "checkmark"
|
||||
| "data-trending"
|
||||
| "design-layout"
|
||||
| "design"
|
||||
| "devices-multiple"
|
||||
| "document-add"
|
||||
| "document-cloud"
|
||||
| "editor"
|
||||
| "eye-dev"
|
||||
| "firework"
|
||||
| "people-shield"
|
||||
| "shield-blue"
|
||||
| "shield-settings"
|
||||
| "tasks"
|
||||
| "text-edit";
|
||||
|
||||
/**
|
||||
* Component for rendering a Fluent image.
|
||||
*/
|
||||
export const FluentImg = ({ name, ...props }: FluentImgProps) => {
|
||||
const { colorMode } = useColorMode();
|
||||
const colorKey = colorMode === "dark" ? "d" : "l";
|
||||
const src = useBaseUrl(`/img/fluent/${name}-${colorKey}-standard-128x128.png`);
|
||||
return <img src={src} {...props} />;
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
.illustration-card {
|
||||
padding: 0;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import { SelectTabData, SelectTabEvent, Tab, TabList } from "@fluentui/react-components";
|
||||
import { ReactNode, useCallback, useState } from "react";
|
||||
import style from "./hero-tabs.module.css";
|
||||
|
||||
export interface HeroProps {
|
||||
tabs: HeroTab[];
|
||||
}
|
||||
|
||||
interface HeroTab {
|
||||
value: string;
|
||||
content: ReactNode;
|
||||
}
|
||||
export const HeroTabs = ({ tabs }: HeroProps) => {
|
||||
const [selected, setSelected] = useState<string>(tabs[0].value);
|
||||
const handleTabSelection = useCallback(
|
||||
(event: SelectTabEvent, data: SelectTabData) => {
|
||||
setSelected(data.value as any);
|
||||
},
|
||||
[setSelected]
|
||||
);
|
||||
const content = tabs.find((tab) => tab.value === selected)?.content;
|
||||
return (
|
||||
<>
|
||||
<div className={style["illustration-card"]}>{content}</div>
|
||||
<TabList selectedValue={selected} onTabSelect={handleTabSelection}>
|
||||
{tabs.map((tab) => {
|
||||
return (
|
||||
<Tab key={tab.value} value={tab.value}>
|
||||
{tab.value}
|
||||
</Tab>
|
||||
);
|
||||
})}
|
||||
</TabList>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
.hero-illustration {
|
||||
width: 1200px;
|
||||
}
|
||||
|
||||
.split-windows {
|
||||
height: 700px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.split-window {
|
||||
flex: 1;
|
||||
min-height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.output {
|
||||
opacity: 90%;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.hero-illustration :global(.theme-code-block) {
|
||||
border-radius: 0;
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
import openapiTsp from "!!raw-loader!@site/static/tsp-samples/openapi3/hero/main.tsp";
|
||||
import openapiYaml from "!!raw-loader!@site/static/tsp-samples/openapi3/hero/out/openapi.yaml";
|
||||
|
||||
import jsonSchemaTsp from "!!raw-loader!@site/static/tsp-samples/json-schema/hero/main.tsp";
|
||||
import jsonSchemaOutput from "!!raw-loader!@site/static/tsp-samples/json-schema/hero/out/schema.yaml";
|
||||
|
||||
import protobufTsp from "!!raw-loader!@site/static/tsp-samples/protobuf/hero/main.tsp";
|
||||
import protobufOutput from "!!raw-loader!@site/static/tsp-samples/protobuf/hero/out/addressbook.proto";
|
||||
|
||||
import CodeBlock from "@theme/CodeBlock";
|
||||
import { HeroTabs } from "../../hero-tabs/hero-tabs";
|
||||
import { Window } from "../../window/window";
|
||||
|
||||
import clsx from "clsx";
|
||||
import style from "./hero-illustration.module.css";
|
||||
|
||||
export const HeroIllustration = () => {
|
||||
return (
|
||||
<HeroTabs
|
||||
tabs={[
|
||||
{
|
||||
value: "Http",
|
||||
content: <OpenAPI3Illustration />,
|
||||
},
|
||||
{ value: "Json Schema", content: <JsonSchemaIllustration /> },
|
||||
{ value: "Protobuf", content: <ProtobufIllustration /> },
|
||||
]}
|
||||
></HeroTabs>
|
||||
);
|
||||
};
|
||||
|
||||
const OpenAPI3Illustration = () => {
|
||||
return (
|
||||
<Window className={style["hero-illustration"]}>
|
||||
<div className={style["split-windows"]}>
|
||||
<CodeBlock className={style["split-window"]} language="tsp" title="main.tsp">
|
||||
{openapiTsp}
|
||||
</CodeBlock>
|
||||
<CodeBlock
|
||||
className={clsx(style["split-window"], style["output"])}
|
||||
language="yaml"
|
||||
title="openapi.yaml"
|
||||
>
|
||||
{openapiYaml}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
const JsonSchemaIllustration = () => {
|
||||
return (
|
||||
<Window className={style["hero-illustration"]}>
|
||||
<div className={style["split-windows"]}>
|
||||
<CodeBlock className={style["split-window"]} language="tsp" title="main.tsp">
|
||||
{jsonSchemaTsp}
|
||||
</CodeBlock>
|
||||
<CodeBlock
|
||||
className={clsx(style["split-window"], style["output"])}
|
||||
language="yaml"
|
||||
title="schema.yaml"
|
||||
>
|
||||
{jsonSchemaOutput}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
const ProtobufIllustration = () => {
|
||||
return (
|
||||
<Window className={style["hero-illustration"]}>
|
||||
<div className={style["split-windows"]}>
|
||||
<CodeBlock className={style["split-window"]} language="tsp" title="main.tsp">
|
||||
{protobufTsp}
|
||||
</CodeBlock>
|
||||
<CodeBlock
|
||||
className={clsx(style["split-window"], style["output"])}
|
||||
language="protobuf"
|
||||
title="addressbook.proto"
|
||||
>
|
||||
{protobufOutput}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
</Window>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,197 @@
|
|||
.hero-divider {
|
||||
height: 2px;
|
||||
align-self: stretch;
|
||||
background: radial-gradient(circle, var(--colorNeutralStroke2) 0%, transparent 80%);
|
||||
}
|
||||
|
||||
.hero-container {
|
||||
background-repeat: no-repeat;
|
||||
width: 100%;
|
||||
background-size: 100vw 94%;
|
||||
|
||||
display: flex;
|
||||
padding: 120px 80px 52px 80px;
|
||||
flex-direction: column;
|
||||
gap: 56px;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
[data-theme="light"] .hero-container {
|
||||
background: radial-gradient(
|
||||
46.56% 45.08% at 56.04% 55.33%,
|
||||
rgba(0, 50, 255, 0.1) 0%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
),
|
||||
radial-gradient(
|
||||
46.69% 41.74% at 69.64% 60.81%,
|
||||
rgba(192, 59, 196, 0.1) 0%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
),
|
||||
radial-gradient(
|
||||
59.78% 45.73% at 30.42% 58.68%,
|
||||
rgba(0, 120, 212, 0.1) 0%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
),
|
||||
radial-gradient(32.53% 31.57% at 50% 66.82%, rgba(70, 54, 104, 0.1) 0%, rgba(0, 0, 0, 0) 100%);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .hero-container {
|
||||
background: radial-gradient(
|
||||
46.56% 45.08% at 56.04% 55.33%,
|
||||
rgba(0, 50, 255, 0.2) 0%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
),
|
||||
radial-gradient(
|
||||
46.69% 41.74% at 69.64% 60.81%,
|
||||
rgba(192, 59, 196, 0.2) 0%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
),
|
||||
radial-gradient(
|
||||
59.78% 45.73% at 30.42% 58.68%,
|
||||
rgba(0, 120, 212, 0.2) 0%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
),
|
||||
radial-gradient(32.53% 31.57% at 50% 66.82%, rgba(70, 54, 104, 0.2) 0%, rgba(0, 0, 0, 0) 100%);
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 100%; /* 24px */
|
||||
letter-spacing: -0.24px;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
text-align: center;
|
||||
font-size: 96px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 100%; /* 96px */
|
||||
letter-spacing: -1px;
|
||||
color: transparent;
|
||||
|
||||
background: linear-gradient(
|
||||
127deg,
|
||||
#6dc2b1 -17.91%,
|
||||
#9ac6e5 19.34%,
|
||||
#3579d8 56.65%,
|
||||
#b145bd 96.75%
|
||||
);
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1024px) {
|
||||
.hero-subtitle {
|
||||
font-size: 68px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 728px) {
|
||||
.hero-subtitle {
|
||||
font-size: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 32px;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.hero-description,
|
||||
.overview-description {
|
||||
max-width: 600px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
align-self: stretch;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hero-demo {
|
||||
display: flex;
|
||||
padding: 0px 102px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex: 1 0 0;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 728px) {
|
||||
.hero-demo {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.overview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 128px;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding: 60px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 728px) {
|
||||
.overview {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.overview-subtitle {
|
||||
text-align: center;
|
||||
font-size: 40px;
|
||||
font-weight: 600;
|
||||
line-height: 48px;
|
||||
}
|
||||
|
||||
.overview-summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.closing {
|
||||
display: flex;
|
||||
padding: 80px 60px;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: flex-start;
|
||||
align-self: baseline;
|
||||
}
|
||||
|
||||
.closing-title {
|
||||
font-size: 40px;
|
||||
font-weight: 600;
|
||||
line-height: 48px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 728px) {
|
||||
.closing-title,
|
||||
.overview-subtitle {
|
||||
font-size: 32px;
|
||||
line-height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.closing-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.codeblock-seperator {
|
||||
height: 10px;
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
import useBaseUrl from "@docusaurus/useBaseUrl";
|
||||
import { Links } from "@site/src/constants";
|
||||
import { DataValidationHeroIllustration } from "@site/src/pages/data-validation";
|
||||
import { OpenAPI3HeroIllustration } from "@site/src/pages/openapi";
|
||||
import { Button } from "../button/button";
|
||||
import { CodeBlock } from "../code-block/code-block";
|
||||
import { FeatureList } from "../feature-list/feature-list";
|
||||
import { LearnMoreCard } from "../learn-more-card/learn-more-card";
|
||||
import { LightDarkImg } from "../light-dark-img/light-dark-img";
|
||||
import { Section } from "../section/section";
|
||||
import { SectionedLayout } from "../sectioned-layout/sectioned-layout";
|
||||
import { DescriptionText, PrimaryText } from "../text/text";
|
||||
import style from "./homepage.module.css";
|
||||
import { OverviewIllustration } from "./overview-illustration/overview-illustration";
|
||||
|
||||
export const HomeContent = () => {
|
||||
return (
|
||||
<>
|
||||
<Hero />
|
||||
<div className={style["hero-divider"]}></div>
|
||||
<SectionedLayout>
|
||||
<Overview />
|
||||
<OpenAPISection />
|
||||
<DataValidationSection />
|
||||
<EditorSection />
|
||||
<ExtensibilitySection />
|
||||
<Closing />
|
||||
</SectionedLayout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Hero = () => {
|
||||
return (
|
||||
<>
|
||||
<div className={style["hero-container"]}>
|
||||
<div className={style["hero-content"]}>
|
||||
<h1 className={style["hero-title"]}>TypeSpec</h1>
|
||||
<div className={style["hero-subtitle"]}>Describe APIs</div>
|
||||
<DescriptionText size="large" className={style["hero-description"]}>
|
||||
Describe your data up front and generate schemas, API specifications, client / server
|
||||
code, docs, and more.
|
||||
</DescriptionText>
|
||||
<div className={style["hero-buttons"]}>
|
||||
<Button as="a" appearance="primary" href={useBaseUrl(Links.docs)}>
|
||||
Get Started
|
||||
</Button>
|
||||
<Button as="a" appearance="outline" href={useBaseUrl(Links.playground)}>
|
||||
Try it out
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["hero-demo"]}>
|
||||
<HeroIllustration />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Overview = () => {
|
||||
return (
|
||||
<>
|
||||
<div className={style["overview"]}>
|
||||
<div className={style["overview-summary"]}>
|
||||
<PrimaryText>Why TypeSpec</PrimaryText>
|
||||
<div className={style["overview-subtitle"]}>API-First for developers</div>
|
||||
<DescriptionText size="large" className={style["overview-description"]}>
|
||||
With TypeSpec, remove the handwritten files that slow you down, and generate
|
||||
standards-compliant API schemas in seconds.
|
||||
</DescriptionText>
|
||||
</div>
|
||||
<Section layout="text-right" illustration={<OverviewIllustration />} itemStyle="plain">
|
||||
<FeatureList
|
||||
items={[
|
||||
{
|
||||
title: "Lightweight language for defining APIs",
|
||||
description:
|
||||
"Inspired by TypeScript, TypeSpec is a minimal language that helps developers describe API shapes in a familiar way.",
|
||||
image: "book-pencil",
|
||||
link: Links.gettingStartedOpenAPI,
|
||||
},
|
||||
{
|
||||
title: "Easy integration with your toolchain",
|
||||
description:
|
||||
"Write TypeSpec, emit to various formats and integrate with their ecosystems.",
|
||||
image: "document-add",
|
||||
},
|
||||
{
|
||||
title: "Multi-protocol support",
|
||||
description:
|
||||
"TypeSpec's standard library includes support for OpenAPI 3.0, JSON Schema 2020-12, Protobuf, and JSON RPC.",
|
||||
image: "tasks",
|
||||
link: "/multi-protocol",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const OpenAPISection = () => {
|
||||
return (
|
||||
<Section
|
||||
header="Productivity"
|
||||
title="Streamline your OpenAPI workflow"
|
||||
description="Benefit from a huge ecosystem of OpenAPI tools for configuring API gateways, generating code, and validating your data."
|
||||
illustration={<OpenAPI3HeroIllustration />}
|
||||
>
|
||||
<LearnMoreCard
|
||||
title="Generate OpenAPI from TypeSpec"
|
||||
image="design"
|
||||
link={Links.useCases.openapi}
|
||||
/>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const DataValidationSection = () => {
|
||||
return (
|
||||
<Section
|
||||
header="Ecosystem"
|
||||
title="Ensure data consistency"
|
||||
description="Defined common models to use across your APIs, use the json schema emitter to get the json schema for your types and use them to validate your data."
|
||||
illustration={<DataValidationHeroIllustration />}
|
||||
>
|
||||
<LearnMoreCard
|
||||
title="Json schema emitter reference"
|
||||
image="people-shield"
|
||||
link={Links.useCases.dataValidation}
|
||||
/>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const EditorSection = () => {
|
||||
return (
|
||||
<Section
|
||||
header="Tooling"
|
||||
title="First party support for code editor"
|
||||
description="Typespec provide built-in support for many common editor features such as syntax highlighting, code completion, and more."
|
||||
illustration={<LightDarkImg src="illustrations/ide-hero" />}
|
||||
>
|
||||
<LearnMoreCard
|
||||
title="Check out our available tooling"
|
||||
image="data-trending"
|
||||
link={Links.useCases.tooling}
|
||||
/>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const ExtensibilitySection = () => {
|
||||
return (
|
||||
<Section
|
||||
header="Extensibility"
|
||||
title="Generate assets in many formats"
|
||||
description="Typespec is built around extensibility, one can write and plugin their own emitter or add custom metadata using a new decorator."
|
||||
illustration={<ExtensibilityIllustration />}
|
||||
>
|
||||
<LearnMoreCard
|
||||
title="Getting started with writing a library"
|
||||
image="data-trending"
|
||||
link={Links.extensibility.gettingStarted}
|
||||
/>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
import extensibilityTs from "!!raw-loader!@site/static/tsp-samples/extensibility/custom-lib.ts";
|
||||
import extensibilityTsp from "!!raw-loader!@site/static/tsp-samples/extensibility/custom-lib.tsp";
|
||||
import { HeroIllustration } from "./hero-illustration/hero-illustration";
|
||||
|
||||
const ExtensibilityIllustration = () => {
|
||||
return (
|
||||
<div>
|
||||
<CodeBlock language="tsp" title="lib.tsp">
|
||||
{extensibilityTsp}
|
||||
</CodeBlock>
|
||||
<div className={style["codeblock-seperator"]}></div>
|
||||
<CodeBlock language="ts" title="lib.ts">
|
||||
{extensibilityTs}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Closing = () => {
|
||||
return (
|
||||
<div className={style["closing"]}>
|
||||
<div className={style["closing-title"]}>Start your TypeSpec journey</div>
|
||||
<DescriptionText>
|
||||
Install the TypeSpec CLI or check out the playground to get started.
|
||||
</DescriptionText>
|
||||
<div className={style["closing-buttons"]}>
|
||||
<Button as="a" appearance="primary" href={useBaseUrl(Links.docs)}>
|
||||
Get Started
|
||||
</Button>
|
||||
<Button as="a" appearance="outline" href={useBaseUrl(Links.playground)}>
|
||||
Try it out
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
.card {
|
||||
width: 723px;
|
||||
height: 542px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.terminal {
|
||||
position: absolute;
|
||||
top: 60px;
|
||||
left: 50px;
|
||||
width: 420px;
|
||||
height: 320px;
|
||||
}
|
||||
|
||||
.ide {
|
||||
position: absolute;
|
||||
top: 226px;
|
||||
left: 183px;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import { IllustrationCard } from "../../illustration-card/illustration-card";
|
||||
import { LightDarkImg } from "../../light-dark-img/light-dark-img";
|
||||
import { P } from "../../painter/painter";
|
||||
import { Terminal } from "../../terminal/terminal";
|
||||
import { Window } from "../../window/window";
|
||||
import style from "./overview-illustration.module.css";
|
||||
|
||||
export const OverviewIllustration = () => {
|
||||
return (
|
||||
<IllustrationCard blend className={style["card"]}>
|
||||
<Terminal className={style["terminal"]}>
|
||||
{P.line(P.secondary("~ /my-project"), " tsp init")}
|
||||
{P.line(" ")}
|
||||
{P.brand("? ")}
|
||||
{"Select a template"}
|
||||
{P.line(" Empty project")}
|
||||
{P.line(P.brand("> "), P.brand.underline("REST API"))}
|
||||
</Terminal>
|
||||
<Window className={style["ide"]}>
|
||||
<LightDarkImg src="illustrations/overview-ide" />
|
||||
</Window>
|
||||
</IllustrationCard>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
.illustration :global(.tabs-container) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.illustration :global(.tabs-container) :global(.margin-top--md) {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.illustration :global(img) {
|
||||
display: block;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import clsx from "clsx";
|
||||
import { Card, CardProps } from "../card/card";
|
||||
import style from "./illustration-card.module.css";
|
||||
|
||||
export interface IllustrationCardProps extends CardProps {}
|
||||
export const IllustrationCard = ({ className, noPadding, ...props }: IllustrationCardProps) => {
|
||||
return (
|
||||
<Card
|
||||
className={clsx(className, style["illustration"])}
|
||||
noPadding={noPadding ?? true}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
.illustration-main {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"tsp openapi"
|
||||
"swagger-ui openapi"
|
||||
"spectral spectral";
|
||||
gap: 5px;
|
||||
background-color: var(--colorNeutralBackground1);
|
||||
}
|
||||
|
||||
.tsp {
|
||||
grid-area: tsp;
|
||||
}
|
||||
.openapi {
|
||||
grid-area: openapi;
|
||||
}
|
||||
.spectral {
|
||||
grid-area: spectral;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import interoperateTsp from "!!raw-loader!@site/static/tsp-samples/openapi3/interoperate/main.tsp";
|
||||
import interoperateOpenapi from "!!raw-loader!@site/static/tsp-samples/openapi3/interoperate/openapi.yaml";
|
||||
import interoperateSpectral from "!!raw-loader!@site/static/tsp-samples/openapi3/interoperate/spectral.txt";
|
||||
import { Painter } from "@site/src/components/painter/painter";
|
||||
import { Terminal } from "@site/src/components/terminal/terminal";
|
||||
import { AssetImg } from "../asset-img/asset-img";
|
||||
import { CodeBlock } from "../code-block/code-block";
|
||||
import { WindowCarousel, WindowCarouselItem } from "../window-carousel/window-carousel";
|
||||
import style from "./interoperate-illustration.module.css";
|
||||
|
||||
export const OpenAPI3InteroperateIllustration = () => {
|
||||
return (
|
||||
<WindowCarousel>
|
||||
<WindowCarouselItem value="TypeSpec">
|
||||
<div className={style["illustration-main"]}>
|
||||
<div className={style["tsp"]}>
|
||||
<CodeBlock language="tsp">{interoperateTsp}</CodeBlock>
|
||||
</div>
|
||||
<div className={style["openapi"]}>
|
||||
<CodeBlock language="yaml">{interoperateOpenapi}</CodeBlock>
|
||||
</div>
|
||||
<Terminal className={style["spectral"]} hideHeader>
|
||||
<Painter content={interoperateSpectral} />
|
||||
</Terminal>
|
||||
</div>
|
||||
</WindowCarouselItem>
|
||||
<WindowCarouselItem value="Swagger UI">
|
||||
<AssetImg className={style["swagger-ui"]} src="illustrations/swagger-ui.png" />
|
||||
</WindowCarouselItem>
|
||||
</WindowCarousel>
|
||||
);
|
||||
};
|
|
@ -1,6 +1,8 @@
|
|||
import BrowserOnly from "@docusaurus/BrowserOnly";
|
||||
import { useColorMode } from "@docusaurus/theme-common";
|
||||
import { FluentProvider, webDarkTheme, webLightTheme } from "@fluentui/react-components";
|
||||
import Layout from "@theme/Layout";
|
||||
import style from "./layouts.module.css";
|
||||
|
||||
export const FluentLayout = ({ children }) => {
|
||||
return (
|
||||
|
@ -10,6 +12,19 @@ export const FluentLayout = ({ children }) => {
|
|||
);
|
||||
};
|
||||
|
||||
export const ShowcaseLayout = ({ children }) => {
|
||||
return (
|
||||
// Need to do this because fluentui can't do SSR simply...
|
||||
<BrowserOnly
|
||||
children={() => (
|
||||
<FluentLayout>
|
||||
<div className={style["showcase-layout"]}>{children}</div>
|
||||
</FluentLayout>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const FluentWrapper = ({ children }) => {
|
||||
const { colorMode } = useColorMode();
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.showcase-layout {
|
||||
background-color: var(--colorNeutralBackground3);
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
.item {
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
border-radius: 14px;
|
||||
background-color: var(--colorNeutralBackground1);
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
text-decoration: none;
|
||||
border-color: var(--colorBrandForeground1);
|
||||
}
|
||||
|
||||
.item:active {
|
||||
text-decoration: none;
|
||||
background-color: var(--colorNeutralBackground2);
|
||||
}
|
||||
|
||||
.item-image {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
max-width: 64px;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import { FluentImageName, FluentImg } from "../fluent-img";
|
||||
import { Link } from "../link/link";
|
||||
import { DescriptionText, NeutralText, Text } from "../text/text";
|
||||
import style from "./learn-more-card.module.css";
|
||||
|
||||
interface LearnMoreCardProps {
|
||||
title: string;
|
||||
description?: string;
|
||||
image?: FluentImageName;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export const LearnMoreCard = ({ title, description, image, link }: LearnMoreCardProps) => {
|
||||
return (
|
||||
<Link href={link} className={style["item"]}>
|
||||
{image && <FluentImg className={style["item-image"]} name={image} />}
|
||||
<div className={style["item-content"]}>
|
||||
<NeutralText>{title}</NeutralText>
|
||||
<DescriptionText>{description}</DescriptionText>
|
||||
<Text>Learn more →</Text>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
:global(html[data-theme="light"]) .dark {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:global(html[data-theme="dark"]) .light {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.light,
|
||||
.dark {
|
||||
display: block;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import { AssetImg } from "../asset-img/asset-img";
|
||||
import style from "./light-dark-img.module.css";
|
||||
|
||||
export const LightDarkImg = ({ src }: { src: string }) => {
|
||||
return (
|
||||
<div>
|
||||
<AssetImg className={style["dark"]} src={`${src}.dark.png`} />
|
||||
<AssetImg className={style["light"]} src={`${src}.light.png`} />
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
import DocusaurusLink from "@docusaurus/Link";
|
||||
import useBaseUrl from "@docusaurus/useBaseUrl";
|
||||
import { ComponentProps } from "react";
|
||||
|
||||
export interface LinkProps extends ComponentProps<"a"> {}
|
||||
|
||||
export const Link = ({ href, children, ...props }: LinkProps) => {
|
||||
return (
|
||||
<DocusaurusLink href={useBaseUrl(href)} {...props}>
|
||||
{children}
|
||||
</DocusaurusLink>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
.color-brand {
|
||||
color: var(--colorBrandForeground1);
|
||||
}
|
||||
.color-secondary {
|
||||
color: var(--colorNeutralForeground3);
|
||||
}
|
||||
.color-warning {
|
||||
color: var(--colorStatusWarningForeground1);
|
||||
}
|
||||
.color-danger {
|
||||
color: var(--colorStatusDangerForeground1);
|
||||
}
|
||||
|
||||
.mod-underline {
|
||||
text-decoration: underline;
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
import clsx from "clsx";
|
||||
import { Fragment, ReactNode } from "react";
|
||||
import style from "./painter.module.css";
|
||||
|
||||
export interface Painter {
|
||||
(...content: ReactNode[]): React.JSX.Element;
|
||||
readonly line: this;
|
||||
readonly brand: this;
|
||||
readonly secondary: this;
|
||||
readonly underline: this;
|
||||
}
|
||||
|
||||
interface PainterOptions {
|
||||
color?: "brand" | "secondary" | "warning";
|
||||
line?: boolean;
|
||||
underline?: boolean;
|
||||
}
|
||||
|
||||
const colors = ["brand", "secondary", "warning"] as const;
|
||||
const modifiers = ["line", "underline"] as const;
|
||||
|
||||
function createPainter(): Painter {
|
||||
return painterFactory({});
|
||||
}
|
||||
|
||||
function painterFactory(options: PainterOptions): Painter {
|
||||
const fn = (...children: ReactNode[]) => {
|
||||
const cls = clsx(
|
||||
style[`color-${options.color}`],
|
||||
...modifiers.map((x) => (options[x] ? style[`mod-${x}`] : undefined))
|
||||
);
|
||||
const content = (
|
||||
<span className={cls}>
|
||||
{children.map((x, i) => (
|
||||
<Fragment key={i}>{x}</Fragment>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
return options.line ? <div>{content}</div> : content;
|
||||
};
|
||||
const styles = {};
|
||||
for (const color of colors) {
|
||||
styles[color] = {
|
||||
get() {
|
||||
return painterFactory({ ...options, color });
|
||||
},
|
||||
};
|
||||
}
|
||||
for (const modifier of modifiers) {
|
||||
styles[modifier] = {
|
||||
get() {
|
||||
return painterFactory({ ...options, [modifier]: true });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Object.defineProperties(fn, styles);
|
||||
return fn as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to highlight custom code.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* [
|
||||
* P.line("Hello ", P.brand("world")),
|
||||
* P.line(P.secondary.underline("Hello back"))
|
||||
* ];
|
||||
* ```
|
||||
*/
|
||||
export const P = createPainter();
|
||||
|
||||
export interface PainterProps {
|
||||
content: string;
|
||||
}
|
||||
|
||||
const painterTagRegex = /\[([a-zA-Z.]+)\](.*)\[\/([a-zA-Z.]+)]/g;
|
||||
export const Painter = ({ content }: PainterProps) => {
|
||||
const replaced = content.matchAll(painterTagRegex);
|
||||
const result = [];
|
||||
let lastIndex = 0;
|
||||
for (const item of replaced) {
|
||||
const [fullMatch, openTag, matchContent, closeTag] = item;
|
||||
|
||||
result.push(content.substring(lastIndex, item.index));
|
||||
lastIndex = item.index + fullMatch.length;
|
||||
|
||||
if (openTag === closeTag) {
|
||||
const segments = openTag.split(".");
|
||||
let p = P;
|
||||
for (const segment of segments) {
|
||||
p = p[segment];
|
||||
}
|
||||
result.push(p(matchContent));
|
||||
} else {
|
||||
result.push(fullMatch);
|
||||
}
|
||||
}
|
||||
result.push(content.substring(lastIndex));
|
||||
return result;
|
||||
};
|
|
@ -0,0 +1,118 @@
|
|||
.section {
|
||||
display: flex;
|
||||
gap: 10px 60px;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1200px) {
|
||||
.section {
|
||||
padding: 0 40px;
|
||||
gap: 3vw;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1500px) {
|
||||
.section {
|
||||
gap: 170px;
|
||||
}
|
||||
}
|
||||
|
||||
.info-container {
|
||||
display: flex;
|
||||
max-width: 100%;
|
||||
padding: 20px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
width: 520px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 56px;
|
||||
}
|
||||
|
||||
.info-heading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-vertical-spacing-xxl, 24px);
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.info-title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--vertical-xl, 20px);
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.info-description {
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.illustration {
|
||||
flex: 1 0 50%;
|
||||
max-width: calc(100% - 40px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.illustration :global(.tabs-container) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.illustration :global(.tabs-container) :global(.margin-top--md) {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.items-list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.items-list-card .item-image {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
max-width: 52px;
|
||||
}
|
||||
|
||||
.items-list-plain {
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
.items-list-card {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.items-list-plain .item-image {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
max-width: 64px;
|
||||
}
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
import clsx from "clsx";
|
||||
import React, { ReactNode } from "react";
|
||||
import { AssetImg } from "../asset-img/asset-img";
|
||||
import { DescriptionText, NeutralText, PrimaryText } from "../text/text";
|
||||
import style from "./section.module.css";
|
||||
|
||||
export interface SectionProps {
|
||||
header?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
illustration: string | React.ReactNode;
|
||||
children?: ReactNode;
|
||||
itemStyle?: "card" | "plain";
|
||||
layout?: "text-left" | "text-right";
|
||||
}
|
||||
|
||||
export const Section = ({
|
||||
header,
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
layout,
|
||||
illustration,
|
||||
itemStyle: itemsCard,
|
||||
}: SectionProps) => {
|
||||
const heading =
|
||||
header || title || description ? (
|
||||
<div className={style["info-heading"]}>
|
||||
<div className={style["info-title"]}>
|
||||
<PrimaryText>{header}</PrimaryText>
|
||||
<NeutralText size="xlarge">{title}</NeutralText>
|
||||
</div>
|
||||
<DescriptionText size="large" className={style["info-description"]}>
|
||||
{description}
|
||||
</DescriptionText>
|
||||
</div>
|
||||
) : undefined;
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
style["section"],
|
||||
style[layout === "text-right" ? "text-right" : "text-left"]
|
||||
)}
|
||||
>
|
||||
<div className={style["info-container"]}>
|
||||
<div className={style["info"]}>
|
||||
{heading}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["illustration"]}>
|
||||
{typeof illustration === "string" ? <AssetImg src={illustration} /> : illustration}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
.layout {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
[data-theme="light"] .layout {
|
||||
background: radial-gradient(
|
||||
154.41% 34.41% at 18.39% 93.33%,
|
||||
rgba(0, 117, 255, 0.1) 0%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
),
|
||||
radial-gradient(
|
||||
83.89% 52.66% at 92.5% 77.61%,
|
||||
rgba(192, 59, 196, 0.07) 0%,
|
||||
rgba(0, 0, 0, 0) 82.67%
|
||||
),
|
||||
linear-gradient(328deg, rgba(0, 120, 212, 0.1) -34.57%, rgba(0, 0, 0, 0) 111.7%);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .layout {
|
||||
background: radial-gradient(
|
||||
154.41% 34.41% at 18.39% 93.33%,
|
||||
rgba(0, 117, 255, 0.2) 0%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
),
|
||||
radial-gradient(
|
||||
83.89% 52.66% at 92.5% 77.61%,
|
||||
rgba(192, 59, 196, 0.14) 0%,
|
||||
rgba(0, 0, 0, 0) 82.67%
|
||||
),
|
||||
linear-gradient(328deg, rgba(0, 120, 212, 0.2) -34.57%, rgba(0, 0, 0, 0) 111.7%);
|
||||
}
|
||||
|
||||
.layout-inner {
|
||||
display: flex;
|
||||
padding: 48px 0px 60px 0px;
|
||||
margin: auto;
|
||||
max-width: 1464px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 128px;
|
||||
align-self: stretch;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import style from "./sectioned-layout.module.css";
|
||||
|
||||
export const SectionedLayout = ({ children }) => {
|
||||
return (
|
||||
<div className={style["layout"]}>
|
||||
<div className={style["layout-inner"]}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
.terminal-code {
|
||||
background-color: var(--window-color);
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
[data-theme="light"] .terminal {
|
||||
--window-color: var(--colorNeutralBackground2);
|
||||
}
|
||||
[data-theme="dark"] .terminal {
|
||||
--window-color: var(--colorNeutralBackground4);
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import clsx from "clsx";
|
||||
import { Window, WindowProps } from "../window/window";
|
||||
import style from "./terminal.module.css";
|
||||
|
||||
export interface TerminalProps extends WindowProps {}
|
||||
|
||||
export const Terminal = ({ className, children, ...props }: WindowProps) => {
|
||||
return (
|
||||
<Window className={clsx(style["terminal"], className)} {...props}>
|
||||
<pre className={style["terminal-code"]}>{children}</pre>
|
||||
</Window>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
.neutral-text {
|
||||
color: var(--colorNeutralForeground1);
|
||||
font-weight: 400;
|
||||
}
|
||||
.description-text {
|
||||
color: var(--colorNeutralForeground3);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.primary-text {
|
||||
color: var(--colorBrandForeground1);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.text {
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
.text-size-standard {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.text-size-large {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.text-size-xlarge {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import clsx from "clsx";
|
||||
import { FunctionComponent } from "react";
|
||||
import style from "./text.module.css";
|
||||
|
||||
export interface TextProps {
|
||||
children: React.ReactNode;
|
||||
size?: "standard" | "large" | "xlarge";
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Text = ({ children, className, size }: TextProps) => {
|
||||
return (
|
||||
<div className={clsx(style["text"], style[`text-size-${size ?? "standard"}`], className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function makeTextComp(clsName: string): FunctionComponent<TextProps> {
|
||||
return ({ className, ...props }: TextProps) => {
|
||||
return <Text {...props} className={clsx(style[clsName], className)} />;
|
||||
};
|
||||
}
|
||||
|
||||
export const NeutralText = makeTextComp("neutral-text");
|
||||
export const PrimaryText = makeTextComp("primary-text");
|
||||
export const DescriptionText = makeTextComp("description-text");
|
|
@ -0,0 +1,20 @@
|
|||
.tryit-codeblock-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tryit-container {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: var(--colorBrandBackground);
|
||||
padding: 0.4rem;
|
||||
text-decoration: none;
|
||||
color: var(--colorNeutralForeground1);
|
||||
}
|
||||
|
||||
.tryit-container:hover {
|
||||
text-decoration: none;
|
||||
color: var(--colorNeutralForeground1);
|
||||
background-color: var(--colorBrandBackgroundHover);
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
import BrowserOnly from "@docusaurus/BrowserOnly";
|
||||
import useBaseUrl from "@docusaurus/useBaseUrl";
|
||||
import { ArrowCircleRight16Filled } from "@fluentui/react-icons";
|
||||
import type CodeBlockDocusaurus from "@theme-original/CodeBlock";
|
||||
import { Props } from "@theme/CodeBlock";
|
||||
import type { CompilerOptions } from "@typespec/compiler";
|
||||
import type { UrlStateStorage } from "@typespec/playground";
|
||||
import type { PlaygroundSaveData } from "@typespec/playground/react";
|
||||
import { AnchorHTMLAttributes, useEffect, useMemo, useState } from "react";
|
||||
import style from "./tsp-tryit.module.css";
|
||||
|
||||
export const withTspPlayground = (Component: typeof CodeBlockDocusaurus) => {
|
||||
function WrappedComponent(props: Props) {
|
||||
if (props.className === "language-tsp" && props.metastring) {
|
||||
const config = findTryitConfig(props.metastring);
|
||||
if (config) {
|
||||
return (
|
||||
<div className={style["tryit-codeblock-container"]}>
|
||||
<PlaygroundTryItLink
|
||||
content={props.children as string}
|
||||
compilerOptions={config}
|
||||
className={style["tryit-container"]}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span>Try it</span>
|
||||
<ArrowCircleRight16Filled />
|
||||
</PlaygroundTryItLink>
|
||||
<Component {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return <Component {...props} />;
|
||||
}
|
||||
|
||||
return WrappedComponent;
|
||||
};
|
||||
|
||||
const tryItRegex = /tryit(=".*")?/;
|
||||
function findTryitConfig(metastring: string): CompilerOptions | undefined {
|
||||
const match = metastring.match(tryItRegex);
|
||||
if (!match) {
|
||||
return undefined;
|
||||
}
|
||||
const optionsString = match[0].split("tryit=")[1]?.slice(1, -1);
|
||||
if (!optionsString) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(optionsString);
|
||||
} catch (e) {
|
||||
throw new Error(`Invalid tryit tsp config: ${optionsString}`);
|
||||
}
|
||||
}
|
||||
|
||||
export interface CodeBlockWithTryItLink extends Props {
|
||||
component: typeof CodeBlockDocusaurus;
|
||||
}
|
||||
|
||||
interface PlaygroundTryItLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
||||
content: string;
|
||||
compilerOptions?: CompilerOptions;
|
||||
}
|
||||
|
||||
const PlaygroundTryItLink = (props: PlaygroundTryItLinkProps) => {
|
||||
return <BrowserOnly children={() => <PlaygroundTryItLinkInternal {...props} />} />;
|
||||
};
|
||||
|
||||
const PlaygroundTryItLinkInternal = ({
|
||||
content,
|
||||
compilerOptions,
|
||||
...props
|
||||
}: PlaygroundTryItLinkProps) => {
|
||||
const [storage, setStorage] = useState<UrlStateStorage<PlaygroundSaveData>>();
|
||||
useEffect(() => {
|
||||
import("@typespec/playground/react")
|
||||
.then(({ createStandalonePlaygroundStateStorage }) => {
|
||||
setStorage(createStandalonePlaygroundStateStorage());
|
||||
})
|
||||
// eslint-disable-next-line no-console
|
||||
.catch((x) => console.error(x));
|
||||
}, []);
|
||||
|
||||
const baseUrl = useBaseUrl("/playground");
|
||||
const playgroundLink = useMemo(() => {
|
||||
return (
|
||||
storage &&
|
||||
baseUrl +
|
||||
"?" +
|
||||
storage.resolveSearchParams({
|
||||
content: content,
|
||||
options: compilerOptions,
|
||||
emitter: compilerOptions.emit?.[0],
|
||||
})
|
||||
);
|
||||
}, [storage, compilerOptions, content]);
|
||||
|
||||
return <a {...props} href={playgroundLink}></a>;
|
||||
};
|
|
@ -0,0 +1,49 @@
|
|||
.feature-group {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
align-self: center;
|
||||
flex-direction: column;
|
||||
padding: 0 80px;
|
||||
}
|
||||
|
||||
.feature {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1200px) {
|
||||
.feature-card {
|
||||
width: 350px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1024px) {
|
||||
.feature-group {
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.feature {
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.content {
|
||||
align-items: baseline;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import Link from "@docusaurus/Link";
|
||||
import { FluentImageName, FluentImg } from "../fluent-img";
|
||||
import { DescriptionText, NeutralText } from "../text/text";
|
||||
import style from "./use-case-feature.module.css";
|
||||
|
||||
export interface UseCaseFeatureProps {
|
||||
image: FluentImageName;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
link: string;
|
||||
}
|
||||
export const UseCaseFeature = ({ image, title, subtitle, link }: UseCaseFeatureProps) => {
|
||||
return (
|
||||
<div className={style["feature"]}>
|
||||
<FluentImg name={image} className={style["image"]} />
|
||||
<div className={style["content"]}>
|
||||
<NeutralText size="large">{title}</NeutralText>
|
||||
<DescriptionText>{subtitle}</DescriptionText>
|
||||
<Link href={link}>Learn more →</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const UseCaseFeatureGroup = ({ children }) => {
|
||||
return <div className={style["feature-group"]}>{children}</div>;
|
||||
};
|
|
@ -0,0 +1,58 @@
|
|||
.container {
|
||||
padding-top: 100px;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
.overview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1 1 50%;
|
||||
padding: 80px;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.title {
|
||||
max-width: 522px;
|
||||
}
|
||||
.subtitle {
|
||||
max-width: 522px;
|
||||
}
|
||||
|
||||
.illustration {
|
||||
width: 40vw;
|
||||
max-height: 100%;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1024px) {
|
||||
.overview {
|
||||
flex-direction: row;
|
||||
}
|
||||
.illustration {
|
||||
width: 50vw;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1200px) {
|
||||
.overview {
|
||||
max-width: 1464px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1023px) {
|
||||
.illustration {
|
||||
display: none;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { Button } from "../button/button";
|
||||
import { DescriptionText, NeutralText } from "../text/text";
|
||||
import style from "./use-case-overview.module.css";
|
||||
|
||||
export interface UseCaseOverviewProps {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
link: string;
|
||||
illustration?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const UseCaseOverview = (props: UseCaseOverviewProps) => {
|
||||
return (
|
||||
<div className={style["container"]}>
|
||||
<div className={style["overview"]}>
|
||||
<div className={style["content"]}>
|
||||
<NeutralText size="xlarge" className={style["title"]}>
|
||||
{props.title}
|
||||
</NeutralText>
|
||||
<div className={style["spacer"]} />
|
||||
<DescriptionText size="large" className={style["subtitle"]}>
|
||||
{props.subtitle}
|
||||
</DescriptionText>
|
||||
<div className={style["spacer"]} />
|
||||
<Button as="a" appearance="primary" href={props.link}>
|
||||
Get started
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className={style["illustration"]}>{props.illustration}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,51 @@
|
|||
import { Children, ReactElement, ReactNode, isValidElement, useMemo } from "react";
|
||||
import { WindowCarouselItemProps } from "./window-carousel";
|
||||
|
||||
export interface CarouselData {
|
||||
items: {
|
||||
value: string;
|
||||
label: string;
|
||||
content: ReactNode;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface UseCarouselOptions {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function useCarousel({ children }: UseCarouselOptions): CarouselData {
|
||||
const items = useMemo(() => {
|
||||
const result = sanitizeTabsChildren(children);
|
||||
return result.map((child) => {
|
||||
return {
|
||||
value: child.props.value,
|
||||
label: child.props.value,
|
||||
content: child.props.children,
|
||||
};
|
||||
});
|
||||
}, [children]);
|
||||
return { items };
|
||||
}
|
||||
|
||||
function sanitizeTabsChildren(children: ReactNode) {
|
||||
return (Children.toArray(children)
|
||||
.filter((child) => child !== "\n")
|
||||
.map((child: any) => {
|
||||
if (!child || (isValidElement(child) && isCarouselItem(child))) {
|
||||
return child;
|
||||
}
|
||||
throw new Error(
|
||||
`Docusaurus error: Bad <WindowCarousel> child <${
|
||||
typeof child.type === "string" ? child.type : child.type.name
|
||||
}>: all children of the <WindowCarousel> component should be <WindowCarouselItem>, and every <WindowCarouselItem> should have a unique "value" prop.`
|
||||
);
|
||||
})
|
||||
?.filter(Boolean) ?? []) as ReactElement<WindowCarouselItemProps>[];
|
||||
}
|
||||
|
||||
function isCarouselItem(
|
||||
comp: ReactElement<unknown>
|
||||
): comp is ReactElement<WindowCarouselItemProps> {
|
||||
const { props } = comp;
|
||||
return !!props && typeof props === "object" && "value" in props;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
.items {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.item {
|
||||
background-color: var(--colorNeutralBackground1);
|
||||
box-shadow: var(--shadow4);
|
||||
box-sizing: border-box;
|
||||
left: 0;
|
||||
transition: left 0.2s linear;
|
||||
width: calc(100% - 100px);
|
||||
}
|
||||
|
||||
.item:not(.item-selected) {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 96px;
|
||||
max-height: calc(100% - 20px);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.item:not(.item-selected):hover {
|
||||
outline: 4px solid var(--colorBrandForeground1);
|
||||
}
|
||||
|
||||
.item-selected {
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.item :global(img) {
|
||||
display: block;
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
import { Tab, TabList } from "@fluentui/react-components";
|
||||
import { Window } from "@site/src/components/window/window";
|
||||
import clsx from "clsx";
|
||||
import { ReactNode, useCallback, useState } from "react";
|
||||
import { useCarousel } from "./use-carousel";
|
||||
import style from "./window-carousel.module.css";
|
||||
|
||||
export interface WindowCarouselProps {
|
||||
defaultValue?: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const WindowCarousel = ({ defaultValue, children }: WindowCarouselProps) => {
|
||||
const { items } = useCarousel({ children });
|
||||
|
||||
const [value, setValue] = useState(defaultValue ?? items[0].value);
|
||||
|
||||
const handleTabChange = useCallback(
|
||||
(_, data) => {
|
||||
setValue(data.value);
|
||||
},
|
||||
[setValue]
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<div className={style["items"]}>
|
||||
{items.map((x) => {
|
||||
return (
|
||||
<WindowCarouselItemRender
|
||||
key={x.value}
|
||||
value={x.value}
|
||||
selected={value === x.value}
|
||||
onSelected={setValue}
|
||||
>
|
||||
{x.content}
|
||||
</WindowCarouselItemRender>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<TabList selectedValue={value} onTabSelect={handleTabChange}>
|
||||
{items.map((x) => (
|
||||
<Tab key={x.value} value={x.value}>
|
||||
{x.label}
|
||||
</Tab>
|
||||
))}
|
||||
</TabList>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export interface WindowCarouselItemRenderProps {
|
||||
value: string;
|
||||
selected: boolean;
|
||||
onSelected: (value: string) => void;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const WindowCarouselItemRender = ({
|
||||
value,
|
||||
onSelected,
|
||||
selected,
|
||||
children,
|
||||
}: WindowCarouselItemRenderProps) => {
|
||||
const onClick = useCallback(() => {
|
||||
onSelected(value);
|
||||
}, [onSelected, value]);
|
||||
return (
|
||||
<Window className={clsx(style["item"], selected && style["item-selected"])} onClick={onClick}>
|
||||
{children}
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
export interface WindowCarouselItemProps {
|
||||
value: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const WindowCarouselItem = ({ children }: WindowCarouselItemProps) => {
|
||||
return <>{children}</>;
|
||||
};
|
|
@ -0,0 +1,49 @@
|
|||
.window {
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--colorNeutralStrokeOnBrand);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: var(--window-color, var(--colorNeutralBackground4));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 35px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.header-divider {
|
||||
height: 1px;
|
||||
align-self: stretch;
|
||||
background: radial-gradient(circle, var(--colorNeutralStroke2) 0%, rgba(82, 82, 82, 0) 80%);
|
||||
}
|
||||
|
||||
.header-right-spacer {
|
||||
width: 52px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-close,
|
||||
.btn-minify,
|
||||
.btn-expand {
|
||||
border-radius: 50%;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
background: #ec6a5e;
|
||||
}
|
||||
.btn-minify {
|
||||
background: #f4bf4f;
|
||||
}
|
||||
|
||||
.btn-expand {
|
||||
background: #62c554;
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import clsx from "clsx";
|
||||
import { MouseEventHandler } from "react";
|
||||
import style from "./window.module.css";
|
||||
|
||||
export interface WindowProps {
|
||||
title?: string;
|
||||
className?: string;
|
||||
hideHeader?: boolean;
|
||||
onClick?: MouseEventHandler<HTMLDivElement>;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Window = ({ className, children, title, hideHeader, ...others }: WindowProps) => {
|
||||
const header = hideHeader ? (
|
||||
""
|
||||
) : (
|
||||
<>
|
||||
<WindowHeader title={title} />
|
||||
<div className={style["header-divider"]} />
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<div className={clsx(style["window"], className)} {...others}>
|
||||
{header}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface WindowRimProps {
|
||||
title?: string;
|
||||
}
|
||||
const WindowHeader = ({ title }: WindowRimProps) => {
|
||||
return (
|
||||
<div className={style["header"]}>
|
||||
<div className={style["actions"]}>
|
||||
<div className={style["btn-close"]}></div>
|
||||
<div className={style["btn-minify"]}></div>
|
||||
<div className={style["btn-expand"]}></div>
|
||||
</div>
|
||||
<div className={style["header-title"]}>{title}</div>
|
||||
<div className={style["header-right-spacer"]}></div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,48 @@
|
|||
export const Links = {
|
||||
useCases: {
|
||||
openapi: "/openapi",
|
||||
dataValidation: "/data-validation",
|
||||
tooling: "/tooling",
|
||||
},
|
||||
gettingStartedOpenAPI: "/docs/getting-started/typespec-for-openapi-dev",
|
||||
gettingStartedWithHttp: "/docs/getting-started/getting-started-http",
|
||||
|
||||
standardLibrary: {
|
||||
decorators: "/docs/standard-library/built-in-decorators",
|
||||
},
|
||||
libraryReferences: {
|
||||
openapi3: {
|
||||
index: "/docs/libraries/openapi3/reference",
|
||||
decorators: "/docs/libraries/openapi3/reference/decorators",
|
||||
},
|
||||
jsonSchema: {
|
||||
index: "/docs/libraries/json-schema/reference",
|
||||
decorators: "/docs/libraries/json-schema/reference/decorators",
|
||||
},
|
||||
},
|
||||
tooling: {
|
||||
formatter: "/docs/handbook/formatter",
|
||||
styleGuide: "/docs/handbook/style-guide",
|
||||
},
|
||||
editor: {
|
||||
home: "/docs/introduction/installation#install-the-vs-and-vscode-extensions",
|
||||
code: "/docs/introduction/editor/code",
|
||||
visualStudio: "/docs/introduction/editor/visual-studio",
|
||||
},
|
||||
extensibility: {
|
||||
gettingStarted: "/docs/extending-typespec/basics",
|
||||
decorators: "/docs/extending-typespec/create-decorators",
|
||||
emitter: "/docs/extending-typespec/emitters",
|
||||
},
|
||||
|
||||
docs: "/docs",
|
||||
playground: "/playground",
|
||||
|
||||
// External
|
||||
spectral: "https://stoplight.io/open-source/spectral",
|
||||
swaggerUI: "https://swagger.io/tools/swagger-ui/",
|
||||
typespecAzure: "https://azure.github.io/typespec-azure",
|
||||
|
||||
// Microsoft links
|
||||
privacy: "https://go.microsoft.com/fwlink/?LinkId=521839",
|
||||
};
|
|
@ -4,28 +4,193 @@
|
|||
* work well for content-centric websites.
|
||||
*/
|
||||
|
||||
/* Fluent Colors for reference */
|
||||
:root {
|
||||
/* Brand Foreground */
|
||||
--colorBrandForeground1: #0f6cbd;
|
||||
--colorBrandForeground2: #115ea3;
|
||||
--colorNeutralForegroundOnBrand: #ffffff;
|
||||
|
||||
/* Brand background */
|
||||
--colorBrandBackground: #0f6cbd;
|
||||
--colorBrandBackgroundHover: #115ea3;
|
||||
|
||||
/* Neutral Foreground */
|
||||
--colorNeutralForeground1: #242424;
|
||||
--colorNeutralForeground2: #424242;
|
||||
--colorNeutralForeground3: #616161;
|
||||
|
||||
/* Neutral background */
|
||||
--colorNeutralBackground1: #ffffff;
|
||||
--colorNeutralBackground2: #fafafa;
|
||||
--colorNeutralBackground3: #f5f5f5;
|
||||
--colorNeutralBackground4: #f0f0f0;
|
||||
|
||||
/* Stroke */
|
||||
--colorNeutralStroke1: #d1d1d1;
|
||||
--colorNeutralStroke2: #e0e0e0;
|
||||
--colorNeutralStroke3: #f0f0f0;
|
||||
--colorNeutralStrokeOnBrand: #ffffff;
|
||||
|
||||
--colorNeutralStroke1Hover: #c7c7c7;
|
||||
|
||||
/* Status warning */
|
||||
--colorStatusWarningBackground1: #fff9f5;
|
||||
--colorStatusWarningBackground2: #fdcfb4;
|
||||
--colorStatusWarningBackground3: #f7630c;
|
||||
--colorStatusWarningForeground1: #bc4b09;
|
||||
--colorStatusWarningForeground2: #8a3707;
|
||||
--colorStatusWarningForeground3: #bc4b09;
|
||||
|
||||
/* Status danger */
|
||||
--colorStatusDangerBackground1: #fdf3f4;
|
||||
--colorStatusDangerBackground2: #eeacb2;
|
||||
--colorStatusDangerBackground3: #c50f1f;
|
||||
--colorStatusDangerForeground1: #b10e1c;
|
||||
--colorStatusDangerForeground2: #6e0811;
|
||||
--colorStatusDangerForeground3: #c50f1f;
|
||||
|
||||
/* Shadows */
|
||||
--shadow2: 0 0 2px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.14);
|
||||
--shadow4: 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.14);
|
||||
--shadow8: 0 0 2px rgba(0, 0, 0, 0.12), 0 4px 8px rgba(0, 0, 0, 0.14);
|
||||
--shadow16: 0 0 2px rgba(0, 0, 0, 0.12), 0 8px 16px rgba(0, 0, 0, 0.14);
|
||||
--shadow28: 0 0 8px rgba(0, 0, 0, 0.12), 0 14px 28px rgba(0, 0, 0, 0.14);
|
||||
--shadow64: 0 0 8px rgba(0, 0, 0, 0.12), 0 32px 64px rgba(0, 0, 0, 0.14);
|
||||
--shadow2Brand: 0 0 2px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.25);
|
||||
--shadow4Brand: 0 0 2px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.25);
|
||||
--shadow8Brand: 0 0 2px rgba(0, 0, 0, 0.3), 0 4px 8px rgba(0, 0, 0, 0.25);
|
||||
--shadow16Brand: 0 0 2px rgba(0, 0, 0, 0.3), 0 8px 16px rgba(0, 0, 0, 0.25);
|
||||
--shadow28Brand: 0 0 8px rgba(0, 0, 0, 0.3), 0 14px 28px rgba(0, 0, 0, 0.25);
|
||||
--shadow64Brand: 0 0 8px rgba(0, 0, 0, 0.3), 0 32px 64px rgba(0, 0, 0, 0.25);
|
||||
|
||||
/* Font weight */
|
||||
--fontWeightRegular: 400;
|
||||
--fontWeightMedium: 500;
|
||||
--fontWeightSemibold: 600;
|
||||
--fontWeightBold: 700;
|
||||
|
||||
/* Stroke width */
|
||||
--strokeWidthThin: 1px;
|
||||
--strokeWidthThick: 2px;
|
||||
--strokeWidthThicker: 3px;
|
||||
--strokeWidthThickest: 4px;
|
||||
|
||||
/* Spacing */
|
||||
--spacingHorizontalNone: 0;
|
||||
--spacingHorizontalXXS: 2px;
|
||||
--spacingHorizontalXS: 4px;
|
||||
--spacingHorizontalSNudge: 6px;
|
||||
--spacingHorizontalS: 8px;
|
||||
--spacingHorizontalMNudge: 10px;
|
||||
--spacingHorizontalM: 12px;
|
||||
--spacingHorizontalL: 16px;
|
||||
--spacingHorizontalXL: 20px;
|
||||
--spacingHorizontalXXL: 24px;
|
||||
--spacingHorizontalXXXL: 32px;
|
||||
--spacingVerticalNone: 0;
|
||||
--spacingVerticalXXS: 2px;
|
||||
--spacingVerticalXS: 4px;
|
||||
--spacingVerticalSNudge: 6px;
|
||||
--spacingVerticalS: 8px;
|
||||
--spacingVerticalMNudge: 10px;
|
||||
--spacingVerticalM: 12px;
|
||||
--spacingVerticalL: 16px;
|
||||
--spacingVerticalXL: 20px;
|
||||
--spacingVerticalXXL: 24px;
|
||||
--spacingVerticalXXXL: 32px;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
/* Brand Foreground */
|
||||
--colorBrandForeground1: #479ef5;
|
||||
--colorBrandForeground2: #62abf5;
|
||||
--colorNeutralForegroundOnBrand: #ffffff;
|
||||
|
||||
/* Brand background */
|
||||
--colorBrandBackground: #115ea3;
|
||||
--colorBrandBackgroundHover: #0f6cbd;
|
||||
|
||||
/* Neutral Foreground */
|
||||
--colorNeutralForeground1: #ffffff;
|
||||
--colorNeutralForeground2: #d6d6d6;
|
||||
--colorNeutralForeground3: #adadad;
|
||||
|
||||
/* Neutral background */
|
||||
--colorNeutralBackground1: #292929;
|
||||
--colorNeutralBackground2: #1f1f1f;
|
||||
--colorNeutralBackground3: #141414;
|
||||
--colorNeutralBackground4: #0a0a0a;
|
||||
|
||||
/* Stroke */
|
||||
--colorNeutralStroke1: #666666;
|
||||
--colorNeutralStroke2: #525252;
|
||||
--colorNeutralStroke3: #3d3d3d;
|
||||
--colorNeutralStrokeOnBrand: #292929;
|
||||
|
||||
--colorNeutralStroke1Hover: #757575;
|
||||
|
||||
/* Status warning */
|
||||
--colorStatusWarningBackground1: #4a1e04;
|
||||
--colorStatusWarningBackground2: #8a3707;
|
||||
--colorStatusWarningBackground3: #f7630c;
|
||||
--colorStatusWarningForeground1: #faa06b;
|
||||
--colorStatusWarningForeground2: #fdcfb4;
|
||||
--colorStatusWarningForeground3: #f98845;
|
||||
|
||||
/* Status danger */
|
||||
--colorStatusDangerBackground1: #3b0509;
|
||||
--colorStatusDangerBackground2: #6e0811;
|
||||
--colorStatusDangerBackground3: #c50f1f;
|
||||
--colorStatusDangerForeground1: #dc626d;
|
||||
--colorStatusDangerForeground2: #eeacb2;
|
||||
--colorStatusDangerForeground3: #dc626d;
|
||||
|
||||
/* Shadows */
|
||||
--shadow2: 0 0 2px rgba(0, 0, 0, 0.24), 0 1px 2px rgba(0, 0, 0, 0.28);
|
||||
--shadow4: 0 0 2px rgba(0, 0, 0, 0.24), 0 2px 4px rgba(0, 0, 0, 0.28);
|
||||
--shadow8: 0 0 2px rgba(0, 0, 0, 0.24), 0 4px 8px rgba(0, 0, 0, 0.28);
|
||||
--shadow16: 0 0 2px rgba(0, 0, 0, 0.24), 0 8px 16px rgba(0, 0, 0, 0.28);
|
||||
--shadow28: 0 0 8px rgba(0, 0, 0, 0.24), 0 14px 28px rgba(0, 0, 0, 0.28);
|
||||
--shadow64: 0 0 8px rgba(0, 0, 0, 0.24), 0 32px 64px rgba(0, 0, 0, 0.28);
|
||||
--shadow2Brand: 0 0 2px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.25);
|
||||
--shadow4Brand: 0 0 2px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.25);
|
||||
--shadow8Brand: 0 0 2px rgba(0, 0, 0, 0.3), 0 4px 8px rgba(0, 0, 0, 0.25);
|
||||
--shadow16Brand: 0 0 2px rgba(0, 0, 0, 0.3), 0 8px 16px rgba(0, 0, 0, 0.25);
|
||||
--shadow28Brand: 0 0 8px rgba(0, 0, 0, 0.3), 0 14px 28px rgba(0, 0, 0, 0.25);
|
||||
--shadow64Brand: 0 0 8px rgba(0, 0, 0, 0.3), 0 32px 64px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
/* You can override the default Infima variables here. */
|
||||
:root {
|
||||
--ifm-color-primary: #2e8555;
|
||||
--ifm-color-primary-dark: #29784c;
|
||||
--ifm-color-primary-darker: #277148;
|
||||
--ifm-color-primary-darkest: #205d3b;
|
||||
--ifm-color-primary-light: #33925d;
|
||||
--ifm-color-primary-lighter: #359962;
|
||||
--ifm-color-primary-lightest: #3cad6e;
|
||||
/* Change docusaurus Colors */
|
||||
--ifm-color-primary: #0f6cbd;
|
||||
--ifm-color-primary-dark: #0d61aa;
|
||||
--ifm-color-primary-darker: #0d5ca1;
|
||||
--ifm-color-primary-darkest: #0a4c84;
|
||||
--ifm-color-primary-light: #1077d0;
|
||||
--ifm-color-primary-lighter: #117cd9;
|
||||
--ifm-color-primary-lightest: #1c8ced;
|
||||
|
||||
--ifm-code-font-size: 95%;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
|
||||
|
||||
/* Header color */
|
||||
--ifm-navbar-background-color: var(--colorNeutralBackground3);
|
||||
}
|
||||
|
||||
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
||||
[data-theme="dark"] {
|
||||
--ifm-color-primary: #25c2a0;
|
||||
--ifm-color-primary-dark: #21af90;
|
||||
--ifm-color-primary-darker: #1fa588;
|
||||
--ifm-color-primary-darkest: #1a8870;
|
||||
--ifm-color-primary-light: #29d5b0;
|
||||
--ifm-color-primary-lighter: #32d8b4;
|
||||
--ifm-color-primary-lightest: #4fddbf;
|
||||
/* Change docusaurus Colors */
|
||||
--ifm-color-primary: #0f6cbd;
|
||||
--ifm-color-primary: #479ef5;
|
||||
--ifm-color-primary-dark: #298ef3;
|
||||
--ifm-color-primary-darker: #1a86f3;
|
||||
--ifm-color-primary-darkest: #0b6fd2;
|
||||
--ifm-color-primary-light: #65aef7;
|
||||
--ifm-color-primary-lighter: #74b6f7;
|
||||
--ifm-color-primary-lightest: #a1cdfa;
|
||||
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
|
@ -41,3 +206,15 @@
|
|||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .header-github-link:before {
|
||||
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='white' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E")
|
||||
no-repeat;
|
||||
}
|
||||
|
||||
.DocSearch-Button {
|
||||
border-radius: 4px !important;
|
||||
|
||||
border: 1px solid var(--neutral-stroke-transparent-rest, rgba(255, 255, 255, 0)) !important;
|
||||
background: var(--colorNeutralBackground1) !important;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# Welcome
|
||||
|
||||
Welcome to the TypeSpec community! We're glad you're here.
|
||||
|
||||
## Github
|
||||
|
||||
We love stars so make sure you [star us on GitHub](https://github.com/microsoft/typespec).
|
||||
|
||||
## Contributing
|
||||
|
||||
Check out our [contributing guide](https://github.com/microsoft/typespec/blob/main/CONTRIBUTING.md) if you would like to contribute to TypeSpec.
|
||||
|
||||
## Discussion
|
||||
|
||||
Ask questions on [Github Discussion](https://github.com/microsoft/typespec/discussions)
|
|
@ -0,0 +1,29 @@
|
|||
.hero {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
gap: 5px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.hero-main {
|
||||
flex: 1 1 50%;
|
||||
max-width: 50%;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.hero-shared {
|
||||
flex: 1 1 50%;
|
||||
max-width: 50%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hero :global(.theme-code-block) {
|
||||
height: 100%;
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import { ShowcaseLayout } from "../components/layouts/fluent-layout";
|
||||
import { Section } from "../components/section/section";
|
||||
import { SectionedLayout } from "../components/sectioned-layout/sectioned-layout";
|
||||
import { UseCaseOverview } from "../components/use-case-overview/use-case-overview";
|
||||
import { Links } from "../constants";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<ShowcaseLayout>
|
||||
<DataValidationContent />
|
||||
</ShowcaseLayout>
|
||||
);
|
||||
}
|
||||
|
||||
const DataValidationContent = () => {
|
||||
return (
|
||||
<div>
|
||||
<SectionedLayout>
|
||||
<UseCaseOverview
|
||||
title="Ensure data consistency"
|
||||
subtitle="Benefit from the reusability and modularity of TypeSpec types to ensure data consistency across your APIs."
|
||||
link={Links.libraryReferences.jsonSchema.index}
|
||||
illustration={<DataValidationHeroIllustration />}
|
||||
/>
|
||||
<Section
|
||||
header="Standard library"
|
||||
title="Use built-in decorators"
|
||||
description="TypeSpec standard library provides decorators for common validation patterns."
|
||||
illustration={<ValidationDecoratorsIllustration />}
|
||||
>
|
||||
<LearnMoreCard
|
||||
title="Standard library reference"
|
||||
description="Browse the standard library reference documentation for details."
|
||||
image="people-shield"
|
||||
link={Links.standardLibrary.decorators}
|
||||
/>
|
||||
</Section>
|
||||
<Section
|
||||
layout="text-right"
|
||||
header="Output"
|
||||
title="Produce Json Schema"
|
||||
description="Benefit from the Json Schema ecosystem to validate your data while writing a much more concise and readable code."
|
||||
illustration={<MultiFileIllustration />}
|
||||
>
|
||||
<LearnMoreCard
|
||||
title="Configure the json schema emitter"
|
||||
description="Change how the json schema is emitted: specify a bundleId to combine all schemas into a single file or use json instead of yaml."
|
||||
image="shield-settings"
|
||||
link={Links.libraryReferences.jsonSchema.index}
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
header="Customize"
|
||||
title="Json Schema Decorators"
|
||||
description="The json schema library provide decorators to customize the output with json schema specific concept."
|
||||
illustration={<JsonSchemaExtensionsIllustration />}
|
||||
>
|
||||
<LearnMoreCard
|
||||
title="Json Schema Decorators Reference"
|
||||
description="Read the reference documentation for available options."
|
||||
image="design"
|
||||
link={Links.libraryReferences.jsonSchema.decorators}
|
||||
/>
|
||||
</Section>
|
||||
</SectionedLayout>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
import commonLibSharedTsp from "!!raw-loader!@site/static/tsp-samples/data-validation/common-lib/common.tsp";
|
||||
import commonLibMainTsp from "!!raw-loader!@site/static/tsp-samples/data-validation/common-lib/main.tsp";
|
||||
export const DataValidationHeroIllustration = () => {
|
||||
return (
|
||||
<IllustrationCard>
|
||||
<Tabs>
|
||||
<TabItem value="main.tsp">
|
||||
<CodeBlock language="tsp">{commonLibMainTsp}</CodeBlock>
|
||||
</TabItem>
|
||||
<TabItem value="common.tsp">
|
||||
<CodeBlock language="tsp">{commonLibSharedTsp}</CodeBlock>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</IllustrationCard>
|
||||
);
|
||||
};
|
||||
|
||||
import multiFileTsp from "!!raw-loader!@site/static/tsp-samples/json-schema/multi-file/main.tsp";
|
||||
import multiFileAddress from "!!raw-loader!@site/static/tsp-samples/json-schema/multi-file/out/Address.yaml";
|
||||
import multiFileCar from "!!raw-loader!@site/static/tsp-samples/json-schema/multi-file/out/Car.yaml";
|
||||
import multiFilePerson from "!!raw-loader!@site/static/tsp-samples/json-schema/multi-file/out/Person.yaml";
|
||||
import { CodeBlock } from "../components/code-block/code-block";
|
||||
|
||||
const MultiFileIllustration = () => {
|
||||
return (
|
||||
<IllustrationCard>
|
||||
<Tabs>
|
||||
<TabItem value="main.tsp">
|
||||
<CodeBlock language="tsp">{multiFileTsp}</CodeBlock>
|
||||
</TabItem>
|
||||
<TabItem value="Address.yaml">
|
||||
<CodeBlock language="yaml">{multiFileAddress}</CodeBlock>
|
||||
</TabItem>
|
||||
<TabItem value="Car.yaml">
|
||||
<CodeBlock language="yaml">{multiFileCar}</CodeBlock>
|
||||
</TabItem>
|
||||
<TabItem value="Person.yaml">
|
||||
<CodeBlock language="yaml">{multiFilePerson}</CodeBlock>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</IllustrationCard>
|
||||
);
|
||||
};
|
||||
|
||||
import extensionsTsp from "!!raw-loader!@site/static/tsp-samples/json-schema/extensions/main.tsp";
|
||||
import extensionsYaml from "!!raw-loader!@site/static/tsp-samples/json-schema/extensions/out/output.yaml";
|
||||
|
||||
const JsonSchemaExtensionsIllustration = () => {
|
||||
return (
|
||||
<IllustrationCard>
|
||||
<Tabs>
|
||||
<TabItem value="main.tsp">
|
||||
<CodeBlock language="tsp">{extensionsTsp}</CodeBlock>
|
||||
</TabItem>
|
||||
<TabItem value="output.yaml">
|
||||
<CodeBlock language="yaml">{extensionsYaml}</CodeBlock>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</IllustrationCard>
|
||||
);
|
||||
};
|
||||
|
||||
import validationDecoratorsTsp from "!!raw-loader!@site/static/tsp-samples/data-validation/validation-decorators.tsp";
|
||||
import { IllustrationCard } from "../components/illustration-card/illustration-card";
|
||||
import { LearnMoreCard } from "../components/learn-more-card/learn-more-card";
|
||||
|
||||
const ValidationDecoratorsIllustration = () => {
|
||||
return (
|
||||
<IllustrationCard>
|
||||
<CodeBlock language="tsp">{validationDecoratorsTsp}</CodeBlock>
|
||||
</IllustrationCard>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
import { HomeContent } from "../components/homepage/homepage";
|
||||
import { ShowcaseLayout } from "../components/layouts/fluent-layout";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<ShowcaseLayout>
|
||||
<HomeContent />
|
||||
</ShowcaseLayout>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
# Multi protocol
|
||||
|
||||
TypeSpec is a protocol agnostic language. It could be used with many different protocols independently or together.
|
||||
|
||||
## Examples
|
||||
|
||||
### Protobuf service and emit a json schema for the models.
|
||||
|
||||
In this example we have a protobuf service and we want to emit a json schema for the models which we can use later to validate the data in our service implementation.
|
||||
|
||||
```tsp tryit="{"emit": ["@typespec/protobuf", "@typespec/json-schema"]}"
|
||||
import "@typespec/protobuf";
|
||||
import "@typespec/http";
|
||||
import "@typespec/json-schema";
|
||||
|
||||
using TypeSpec.Protobuf;
|
||||
using TypeSpec.Http;
|
||||
|
||||
@JsonSchema.jsonSchema
|
||||
@Protobuf.package({
|
||||
name: "kiosk",
|
||||
})
|
||||
@TypeSpec.service
|
||||
namespace KioskExample;
|
||||
|
||||
// models.tsp
|
||||
model Kiosk {
|
||||
@field(1) id?: int32;
|
||||
@field(2) name: string;
|
||||
@field(3) size: ScreenSize;
|
||||
@field(4) location: LatLng;
|
||||
@field(5) create_time?: int32;
|
||||
}
|
||||
model ScreenSize {
|
||||
@field(1) width: int32;
|
||||
@field(2) height: int32;
|
||||
}
|
||||
|
||||
model LatLng {
|
||||
@field(1) lat: float64;
|
||||
@field(2) lng: float64;
|
||||
}
|
||||
|
||||
model ListResponse {
|
||||
@field(1) kiosks: Kiosk[];
|
||||
}
|
||||
|
||||
@Protobuf.service
|
||||
interface Kiosks {
|
||||
@post createKiosk(...Kiosk): Kiosk;
|
||||
@list listKiosks(): ListResponse;
|
||||
@get getKiosk(@path @field(1) id: int32): Kiosk;
|
||||
@delete deleteKiosk(@path @field(1) id: int32): void;
|
||||
}
|
||||
```
|
|
@ -0,0 +1,91 @@
|
|||
import abstractionCode from "!!raw-loader!@site/static/tsp-samples/openapi3/abstraction.tsp";
|
||||
import { Links } from "@site/src/constants";
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
import { CodeBlock } from "../../components/code-block/code-block";
|
||||
import { ShowcaseLayout } from "../../components/layouts/fluent-layout";
|
||||
import { Section } from "../../components/section/section";
|
||||
import { SectionedLayout } from "../../components/sectioned-layout/sectioned-layout";
|
||||
import { UseCaseOverview } from "../../components/use-case-overview/use-case-overview";
|
||||
import style from "./openapi.module.css";
|
||||
export default function Home() {
|
||||
return (
|
||||
<ShowcaseLayout>
|
||||
<OpenApiContent />
|
||||
</ShowcaseLayout>
|
||||
);
|
||||
}
|
||||
|
||||
const OpenApiContent = () => {
|
||||
return (
|
||||
<div>
|
||||
<UseCaseOverview
|
||||
title="Write TypeSpec, emit OpenAPI"
|
||||
subtitle="Benefit from a huge ecosystem of OpenAPI tools for configuring API gateways, generating code, and validating your data."
|
||||
link={Links.gettingStartedOpenAPI}
|
||||
illustration={<OpenAPI3HeroIllustration />}
|
||||
/>
|
||||
<SectionedLayout>
|
||||
<Section
|
||||
header="Ecosystem"
|
||||
title="Interoperate with the OpenAPI ecosystem"
|
||||
description="Write TypeSpec, emit OpenAPI. Benefit from a huge ecosystem of OpenAPI tools for configuring API gateways, generating code, and validating your data."
|
||||
illustration={<OpenAPI3InteroperateIllustration />}
|
||||
>
|
||||
<LearnMoreCard
|
||||
title="Linters"
|
||||
description="Integrate with spectral to lint your OpenAPI."
|
||||
image="shield-blue"
|
||||
link={Links.spectral}
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
layout="text-right"
|
||||
header="Ecosystem"
|
||||
title="Abstract common patterns"
|
||||
description="Codify API patterns into reusable components, improving up quality and consistency across your API surface."
|
||||
illustration={<OpenAPI3AbstractCode />}
|
||||
>
|
||||
<LearnMoreCard
|
||||
title="Example: TypeSpec Azure Library"
|
||||
description="Azure library for TypeSpec allows a multitude of teams to reuse approved patterns."
|
||||
image="document-cloud"
|
||||
link={Links.typespecAzure}
|
||||
/>
|
||||
</Section>
|
||||
</SectionedLayout>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
import heroMainTsp from "!!raw-loader!@site/static/tsp-samples/openapi3/hero/main.tsp";
|
||||
import heroOpenAPIYaml from "!!raw-loader!@site/static/tsp-samples/openapi3/hero/out/openapi.yaml";
|
||||
import { IllustrationCard } from "../../components/illustration-card/illustration-card";
|
||||
import { OpenAPI3InteroperateIllustration } from "../../components/interoperate-illustration/interoperate-illustration";
|
||||
import { LearnMoreCard } from "../../components/learn-more-card/learn-more-card";
|
||||
|
||||
export const OpenAPI3HeroIllustration = () => {
|
||||
return (
|
||||
<IllustrationCard>
|
||||
<Tabs>
|
||||
<TabItem value="main.tsp">
|
||||
<CodeBlock language="tsp">{heroMainTsp}</CodeBlock>
|
||||
</TabItem>
|
||||
<TabItem value="openapi.yaml">
|
||||
<div className={style["hero-openapi"]}>
|
||||
<CodeBlock language="yaml">{heroOpenAPIYaml}</CodeBlock>
|
||||
</div>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</IllustrationCard>
|
||||
);
|
||||
};
|
||||
|
||||
export const OpenAPI3AbstractCode = () => {
|
||||
return (
|
||||
<IllustrationCard>
|
||||
<CodeBlock language="tsp">{abstractionCode}</CodeBlock>
|
||||
</IllustrationCard>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
.hero-openapi {
|
||||
max-height: 50vh;
|
||||
overflow-y: auto;
|
||||
}
|
|
@ -2,7 +2,7 @@ import BrowserOnly from "@docusaurus/BrowserOnly";
|
|||
import { useEffect, useState } from "react";
|
||||
|
||||
import "@typespec/playground/styles.css";
|
||||
import { FluentLayout } from "../components/fluent-layout/fluent-layout";
|
||||
import { FluentLayout } from "../components/layouts/fluent-layout";
|
||||
import { VersionData, loadImportMap } from "../components/playground-component/import-map";
|
||||
import { LoadingSpinner } from "../components/playground-component/loading-spinner";
|
||||
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
import useBaseUrl from "@docusaurus/useBaseUrl";
|
||||
import Layout from "@theme/Layout";
|
||||
|
||||
export default function Playground() {
|
||||
return (
|
||||
<Layout>
|
||||
<div
|
||||
className="play-iframe"
|
||||
style={{
|
||||
display: "flex",
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "80%",
|
||||
}}
|
||||
>
|
||||
{
|
||||
<iframe
|
||||
title="TypeSpec Specification"
|
||||
src={useBaseUrl("/spec.html")}
|
||||
style={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
}}
|
||||
></iframe>
|
||||
}
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
.formatter-illustration {
|
||||
--animation-duration: 6s;
|
||||
width: 100%;
|
||||
height: 420px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.formatter-illustration > * {
|
||||
width: 100%;
|
||||
height: 416px;
|
||||
}
|
||||
|
||||
.formatter-illustration-formatted {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
animation: fade var(--animation-duration) infinite alternate;
|
||||
}
|
||||
|
||||
.formatter-illustration-codeblock {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@keyframes fade {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
40% {
|
||||
opacity: 0;
|
||||
}
|
||||
60% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 200;
|
||||
padding: 2px 5px;
|
||||
border-radius: 4px;
|
||||
color: #fefefe;
|
||||
}
|
||||
|
||||
.formatter-illustration-unformatted .badge {
|
||||
background-color: var(--colorStatusWarningBackground3);
|
||||
animation: fade var(--animation-duration) infinite alternate-reverse;
|
||||
}
|
||||
.formatter-illustration-formatted .badge {
|
||||
background-color: var(--ifm-color-primary);
|
||||
animation: fade var(--animation-duration) infinite alternate;
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
import { CodeBlock } from "../components/code-block/code-block";
|
||||
import { ShowcaseLayout } from "../components/layouts/fluent-layout";
|
||||
import { Section } from "../components/section/section";
|
||||
import { SectionedLayout } from "../components/sectioned-layout/sectioned-layout";
|
||||
import { UseCaseOverview } from "../components/use-case-overview/use-case-overview";
|
||||
import style from "./tooling.module.css";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<ShowcaseLayout>
|
||||
<DataValidationContent />
|
||||
</ShowcaseLayout>
|
||||
);
|
||||
}
|
||||
|
||||
const DataValidationContent = () => {
|
||||
return (
|
||||
<div>
|
||||
<UseCaseOverview
|
||||
title="Syntax highlighting, autocomplete, formatter and more"
|
||||
subtitle="Typespec comes out of the box with many crucial tooling that will improve your productivity."
|
||||
link={Links.editor.home}
|
||||
illustration={<LightDarkImg src="illustrations/ide-hero" />}
|
||||
/>
|
||||
<SectionedLayout>
|
||||
<Section
|
||||
header="Style consistency"
|
||||
title="Built-in formatter"
|
||||
description="TypeSpec provide an opinionated formatter that enables you to enforce a consistent style in your codebase."
|
||||
illustration={<FormatterIllustration />}
|
||||
>
|
||||
<LearnMoreCard
|
||||
title="Formatter usage"
|
||||
description="See documentation on how to use the formatter."
|
||||
image="text-edit"
|
||||
link={Links.tooling.formatter}
|
||||
/>
|
||||
</Section>
|
||||
<Section
|
||||
layout="text-right"
|
||||
header="Warning"
|
||||
title="Warning and errors"
|
||||
description="Errors and warnings in your spec are reported as you type."
|
||||
illustration={<LightDarkImg src="illustrations/warnings-and-errors" />}
|
||||
>
|
||||
<LearnMoreCard
|
||||
title="Extension installation"
|
||||
description="See documentation on how to install editor extensions."
|
||||
image="text-edit"
|
||||
link={Links.editor.home}
|
||||
/>
|
||||
</Section>
|
||||
<Section
|
||||
header="Intellisense"
|
||||
title="Autocomplete and more"
|
||||
description="IntelliSense shows you intelligent code completion, hover information, and signature help so that you can write code more quickly and correctly."
|
||||
illustration={<LightDarkImg src="illustrations/autocomplete" />}
|
||||
/>
|
||||
<Section
|
||||
layout="text-right"
|
||||
header="Refactor"
|
||||
title="Bulk renaming"
|
||||
description="One of the simplest refactoring is to rename a reference. You can rename a identifier and see all its reference across your TypeSpec project update."
|
||||
illustration={
|
||||
<video
|
||||
src={useBaseUrl("/img/illustrations/refactor.mp4")}
|
||||
autoPlay={true}
|
||||
loop={true}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</SectionedLayout>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
import notFormattedTsp from "!!raw-loader!@site/static/tsp-samples/tooling/formatter/file.noformat.tsp";
|
||||
import formattedTsp from "!!raw-loader!@site/static/tsp-samples/tooling/formatter/formatted.tsp";
|
||||
import useBaseUrl from "@docusaurus/useBaseUrl";
|
||||
import { LearnMoreCard } from "../components/learn-more-card/learn-more-card";
|
||||
import { LightDarkImg } from "../components/light-dark-img/light-dark-img";
|
||||
import { Links } from "../constants";
|
||||
|
||||
const FormatterIllustration = () => {
|
||||
return (
|
||||
<div className={style["formatter-illustration"]}>
|
||||
<div className={style["formatter-illustration-unformatted"]}>
|
||||
<div className={style["badge"]}>Unformatted</div>
|
||||
<CodeBlock language="tsp" className={style["formatter-illustration-codeblock"]}>
|
||||
{notFormattedTsp}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
<div className={style["formatter-illustration-formatted"]}>
|
||||
<div className={style["badge"]}>Formatted</div>
|
||||
<CodeBlock language="tsp" className={style["formatter-illustration-codeblock"]}>
|
||||
{formattedTsp}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
import { withTspPlayground } from "@site/src/components/tsp-tryit-codeblock/with-tsp-playground";
|
||||
import CodeBlockDocusaurus from "@theme-original/CodeBlock";
|
||||
|
||||
export default withTspPlayground(CodeBlockDocusaurus);
|
|
@ -0,0 +1,92 @@
|
|||
.footer {
|
||||
padding: 80px;
|
||||
color: var(--colorNeutralForeground2);
|
||||
background-color: var(--colorNeutralBackground3);
|
||||
max-width: 100vw;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 728px) {
|
||||
.footer {
|
||||
padding: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.top {
|
||||
display: flex;
|
||||
gap: 60px;
|
||||
}
|
||||
|
||||
.top-separator {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
width: 276px;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
color: var(--colorNeutralForeground1);
|
||||
line-height: 22px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.separator {
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 728px) {
|
||||
.links {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.general-links :global(a) {
|
||||
color: var(--colorNeutralForeground2);
|
||||
text-decoration: underline;
|
||||
}
|
||||
.general-links :global(a:hover) {
|
||||
color: var(--colorBrandForeground1);
|
||||
}
|
||||
|
||||
.theme-picker {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 4px 6px;
|
||||
gap: 6px;
|
||||
border: 1px solid var(--colorNeutralStroke1);
|
||||
border-radius: 70px;
|
||||
}
|
||||
|
||||
.theme-option {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: 2px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.theme-option:hover {
|
||||
color: var(--colorNeutralForeground1);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: var(--colorBrandForeground1);
|
||||
border-radius: 50%;
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
import { useColorMode } from "@docusaurus/theme-common";
|
||||
import { WeatherMoon20Regular, WeatherSunny20Regular } from "@fluentui/react-icons";
|
||||
import { Link } from "@site/src/components/link/link";
|
||||
import { Links } from "@site/src/constants";
|
||||
import clsx from "clsx";
|
||||
import style from "./index.module.css";
|
||||
|
||||
export default function FooterLayout({ style: theme, links, copyright }) {
|
||||
return (
|
||||
<footer
|
||||
className={clsx(style["footer"], {
|
||||
"footer--dark": theme === "dark",
|
||||
})}
|
||||
data-theme="dark"
|
||||
>
|
||||
<div className={style["top"]}>
|
||||
<div className={style["main"]}>
|
||||
<div className={style["main-title"]}>TypeSpec</div>
|
||||
<div className={style["main-description"]}>
|
||||
Follow us for latest updates, contributions, and more.
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
href="https://github.com/microsoft/typespec"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="header-github-link"
|
||||
aria-label="Github repository"
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["top-separator"]}></div>
|
||||
<div className={style["links"]}>{links}</div>
|
||||
<div>
|
||||
<ThemePicker />
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["separator"]}></div>
|
||||
<div className={style["bottom"]}>
|
||||
<div className={style["logo-copyright"]}>
|
||||
<div className={style["logo"]}>
|
||||
<MicrosoftLogo />
|
||||
Microsoft
|
||||
</div>
|
||||
<div>{copyright}</div>
|
||||
</div>
|
||||
<div className={style["general-links"]}>
|
||||
<Link href={Links.privacy} title="Microsoft Privacy Policy">
|
||||
Privacy
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
const MicrosoftLogo = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M7.66683 1.33325H1.3335V7.66659H7.66683V1.33325Z" />
|
||||
<path d="M14.6668 1.33325H8.3335V7.66659H14.6668V1.33325Z" />
|
||||
<path d="M8.3335 8.33325H14.6668V14.6666H8.3335V8.33325Z" />
|
||||
<path d="M7.66683 8.33325H1.3335V14.6666H7.66683V8.33325Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const ThemePicker = () => {
|
||||
const { colorMode, setColorMode } = useColorMode();
|
||||
|
||||
return (
|
||||
<div className={style["theme-picker"]}>
|
||||
<div
|
||||
title="Light mode"
|
||||
className={clsx(style["theme-option"], { [style["selected"]]: colorMode === "light" })}
|
||||
onClick={() => setColorMode("light")}
|
||||
>
|
||||
<WeatherSunny20Regular />
|
||||
</div>
|
||||
<div
|
||||
title="Dark mode"
|
||||
className={clsx(style["theme-option"], { [style["selected"]]: colorMode === "dark" })}
|
||||
onClick={() => setColorMode("dark")}
|
||||
>
|
||||
<WeatherMoon20Regular />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -28,7 +28,7 @@ const lang = {
|
|||
},
|
||||
|
||||
property: {
|
||||
pattern: /((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,
|
||||
pattern: /\b[A-Za-z]\w*(?=\s*:)/m,
|
||||
lookbehind: true,
|
||||
alias: "property",
|
||||
},
|
||||
|
@ -68,7 +68,7 @@ const lang = {
|
|||
/\b(?:import|model|scalar|namespace|op|interface|union|using|is|extends|enum|alias|return|void|never|if|else|projection|dec|extern|fn)\b/,
|
||||
|
||||
function: /\b[a-z_]\w*(?=[ \t]*\()/i,
|
||||
variable: /\b(?:[A-Z_\d]*[a-z]\w*)?\b/,
|
||||
variable: /\b[A-Za-z]\w*\b/,
|
||||
|
||||
number: /(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:E[+-]?\d+)?/i,
|
||||
operator:
|
||||
|
|
После Ширина: | Высота: | Размер: 5.6 KiB |
После Ширина: | Высота: | Размер: 5.7 KiB |
После Ширина: | Высота: | Размер: 6.0 KiB |
После Ширина: | Высота: | Размер: 6.6 KiB |
После Ширина: | Высота: | Размер: 4.7 KiB |
После Ширина: | Высота: | Размер: 5.1 KiB |
После Ширина: | Высота: | Размер: 5.4 KiB |
После Ширина: | Высота: | Размер: 5.7 KiB |
После Ширина: | Высота: | Размер: 5.4 KiB |