Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FE] Prep work - Tailwind + shadcn cross integration in LSO + LSE #6870

Open
wants to merge 16 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions Dockerfile.development
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# syntax=docker/dockerfile:1
ARG NODE_VERSION=18
ARG PYTHON_VERSION=3.12
ARG POETRY_VERSION=1.8.4
ARG VERSION_OVERRIDE
ARG BRANCH_OVERRIDE

################################ Overview

# This Dockerfile builds a Label Studio environment.
# It consists of three main stages:
# 1. "frontend-builder" - Compiles the frontend assets using Node.
# 2. "frontend-version-generator" - Generates version files for frontend sources.
# 3. "venv-builder" - Prepares the virtualenv environment.
# 4. "py-version-generator" - Generates version files for python sources.
# 5. "prod" - Creates the final production image with the Label Studio, Nginx, and other dependencies.

################################ Stage: frontend-builder (build frontend assets)
FROM --platform=${BUILDPLATFORM} node:${NODE_VERSION} AS frontend-builder
WORKDIR /label-studio/web

COPY web .
COPY pyproject.toml ../pyproject.toml

################################ Stage: venv-builder (prepare the virtualenv)
FROM python:${PYTHON_VERSION}-slim AS venv-builder
ARG POETRY_VERSION

ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=off \
PIP_DISABLE_PIP_VERSION_CHECK=on \
PIP_DEFAULT_TIMEOUT=100 \
PIP_CACHE_DIR="/.cache" \
POETRY_CACHE_DIR="/.poetry-cache" \
POETRY_HOME="/opt/poetry" \
POETRY_VIRTUALENVS_IN_PROJECT=true \
PATH="/opt/poetry/bin:$PATH"

ADD https://install.python-poetry.org /tmp/install-poetry.py
RUN python /tmp/install-poetry.py

RUN --mount=type=cache,target="/var/cache/apt",sharing=locked \
--mount=type=cache,target="/var/lib/apt/lists",sharing=locked \
set -eux; \
apt-get update; \
apt-get install --no-install-recommends -y \
build-essential git; \
apt-get autoremove -y

WORKDIR /label-studio

ENV VENV_PATH="/label-studio/.venv"
ENV PATH="$VENV_PATH/bin:$PATH"

## Starting from this line all packages will be installed in $VENV_PATH

# Copy dependency files
COPY pyproject.toml poetry.lock README.md ./

# Install dependencies without dev packages
RUN --mount=type=cache,target=$POETRY_CACHE_DIR,sharing=locked \
poetry check --lock && poetry install --no-root --without test --extras uwsgi

# Install LS
COPY label_studio label_studio
RUN --mount=type=cache,target=$POETRY_CACHE_DIR,sharing=locked \
# `--extras uwsgi` is mandatory here due to poetry bug: https://github.com/python-poetry/poetry/issues/7302
poetry install --only-root --extras uwsgi && \
python3 label_studio/manage.py collectstatic --no-input

################################ Stage: py-version-generator
FROM venv-builder AS py-version-generator
ARG VERSION_OVERRIDE
ARG BRANCH_OVERRIDE

# Create version_.py and ls-version_.py
RUN --mount=type=bind,source=.git,target=./.git \
VERSION_OVERRIDE=${VERSION_OVERRIDE} BRANCH_OVERRIDE=${BRANCH_OVERRIDE} poetry run python label_studio/core/version.py

################################### Stage: prod
FROM python:${PYTHON_VERSION}-slim AS production

ENV LS_DIR=/label-studio \
HOME=/label-studio \
LABEL_STUDIO_BASE_DATA_DIR=/label-studio/data \
OPT_DIR=/opt/heartex/instance-data/etc \
PATH="/label-studio/.venv/bin:$PATH" \
DJANGO_SETTINGS_MODULE=core.settings.label_studio \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1

WORKDIR $LS_DIR

# install prerequisites for app
RUN --mount=type=cache,target="/var/cache/apt",sharing=locked \
--mount=type=cache,target="/var/lib/apt/lists",sharing=locked \
set -eux; \
apt-get update; \
apt-get upgrade -y; \
apt-get install --no-install-recommends -y libexpat1 \
gnupg2 curl; \
apt-get autoremove -y

# install nginx
RUN --mount=type=cache,target="/var/cache/apt",sharing=locked \
--mount=type=cache,target="/var/lib/apt/lists",sharing=locked \
set -eux; \
curl -sSL https://nginx.org/keys/nginx_signing.key | gpg --dearmor -o /etc/apt/keyrings/nginx-archive-keyring.gpg >/dev/null; \
DEBIAN_VERSION=$(awk -F '=' '/^VERSION_CODENAME=/ {print $2}' /etc/os-release); \
printf "deb [signed-by=/etc/apt/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/debian ${DEBIAN_VERSION} nginx\n" > /etc/apt/sources.list.d/nginx.list; \
printf "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" > /etc/apt/preferences.d/99nginx; \
apt-get update; \
apt-get install --no-install-recommends -y nginx; \
apt-get autoremove -y

RUN set -eux; \
mkdir -p $LS_DIR $LABEL_STUDIO_BASE_DATA_DIR $OPT_DIR && \
chown -R 1001:0 $LS_DIR $LABEL_STUDIO_BASE_DATA_DIR $OPT_DIR /var/log/nginx /etc/nginx

COPY --chown=1001:0 deploy/default.conf /etc/nginx/nginx.conf

# Copy essential files for installing Label Studio and its dependencies
COPY --chown=1001:0 pyproject.toml .
COPY --chown=1001:0 poetry.lock .
COPY --chown=1001:0 README.md .
COPY --chown=1001:0 LICENSE LICENSE
COPY --chown=1001:0 licenses licenses
COPY --chown=1001:0 deploy deploy

# Copy files from build stages
COPY --chown=1001:0 --from=venv-builder $LS_DIR $LS_DIR
COPY --chown=1001:0 --from=py-version-generator $LS_DIR/label_studio/core/version_.py $LS_DIR/label_studio/core/version_.py
COPY --chown=1001:0 --from=frontend-builder $LS_DIR/web/dist $LS_DIR/web/dist

USER 1001

EXPOSE 8080

ENTRYPOINT ["./deploy/docker-entrypoint.sh"]
CMD ["label-studio"]
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dependencies": {
"shadcn": "2.1.6"
}
}
2 changes: 1 addition & 1 deletion web/.stylelintrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"extends": "stylelint-config-standard-scss",
"extends": ["stylelint-config-standard-scss", "stylelint-config-tailwindcss", "stylelint-config-tailwindcss/scss"],
"rules": {
"selector-class-pattern": null,
"custom-property-pattern": null,
Expand Down
1 change: 1 addition & 0 deletions web/apps/labelstudio/src/app/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import ErrorBoundary from "./ErrorBoundary";
import { RootPage } from "./RootPage";
import { FF_OPTIC_2, FF_UNSAVED_CHANGES, isFF } from "../utils/feature-flags";
import { ToastProvider, ToastViewport } from "@humansignal/ui";
import "@humansignal/ui/src/tailwind.css";

const baseURL = new URL(APP_SETTINGS.hostname || location.origin);
export const UNBLOCK_HISTORY_MESSAGE = "UNBLOCK_HISTORY";
Expand Down
7 changes: 6 additions & 1 deletion web/apps/labelstudio/src/components/Menubar/Menubar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,12 @@ export const Menubar = ({ enabled, defaultOpened, defaultPinned, children, onSid
<div className={menubarClass}>
<Dropdown.Trigger dropdown={menuDropdownRef} closeOnClickOutside={!sidebarPinned}>
<div className={`${menubarClass.elem("trigger")} main-menu-trigger`}>
<img src={absoluteURL("/static/icons/logo.svg")} alt="Label Studio Logo" height="22" />
<img
src={absoluteURL("/static/icons/logo.svg")}
alt="Label Studio Logo"
height="22"
style={{ height: 22 }}
/>
<Hamburger opened={sidebarOpened} />
</div>
</Dropdown.Trigger>
Expand Down
3 changes: 2 additions & 1 deletion web/apps/labelstudio/src/pages/Settings/GeneralSettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export const GeneralSettings = () => {
return (
<Block name="general-settings">
<Elem name={"wrapper"}>
<h1>General Settings</h1>
<h1>General Settings!</h1>

Comment on lines +31 to +32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<h1>General Settings!</h1>
<h1>General Settings</h1>

Looks like example changes that were committed on accident.

<Block name="settings-wrapper">
<Form action="updateProject" formData={{ ...project }} params={{ pk: project.id }} onSubmit={updateProject}>
<Form.Row columnCount={1} rowGap="16px">
Expand Down
21 changes: 21 additions & 0 deletions web/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "libs/ui/src/tailwind.config.js",
"css": "libs/ui/src/tailwind.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@humansignal/shad/components",
"ui": "@humansignal/shad/components/ui",
"utils": "@humansignal/shad/utils",
"lib": "@humansignal/shad/lib",
"hooks": "@humansignal/shad/hooks"
},
"iconLibrary": "lucide"
}
33 changes: 33 additions & 0 deletions web/libs/storybook/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { StorybookConfig } from "@storybook/react-webpack5";

const config: StorybookConfig = {
core: { builder: "@storybook/builder-webpack5" },
stories: ["../../../libs/**/*.stories.@(js|jsx|ts|tsx|mdx)", "../../../apps/**/*.stories.@(js|jsx|ts|tsx|mdx)"],
addons: ["@storybook/addon-essentials", "@storybook/addon-interactions", "@nx/react/plugins/storybook"],
framework: {
name: "@storybook/react-webpack5",
options: {},
},

webpackFinal: async (config) => {
config.module.rules.push({
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
{
loader: "file-loader",
options: {
name: "[name].[ext]",
outputPath: "fonts/",
},
},
],
});
return config;
},
};

export default config;

// To customize your webpack configuration you can use the webpackFinal field.
// Check https://storybook.js.org/docs/react/builders/webpack#extending-storybooks-webpack-config
// and https://nx.dev/recipes/storybook/custom-builder-configs
3 changes: 3 additions & 0 deletions web/libs/storybook/.storybook/preview.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@import '../src/tailwind.css';
@import '../src/tokens/colors';
@import '../src/tokens/typography';
3 changes: 3 additions & 0 deletions web/libs/storybook/.storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import "./preview.scss";

export const parameters = {};
18 changes: 18 additions & 0 deletions web/libs/storybook/button/button.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "../../ui/src/shad/components/ui/button.tsx";

const meta: Meta<typeof Button> = {
component: Button,
render: ({ form, ...args }) => {
return <Button {...args}>Hello</Button>;
},
};

export default meta;
type Story = StoryObj<typeof Button>;

export const Primary: Story = {
args: {
variant: "default",
},
};
42 changes: 42 additions & 0 deletions web/libs/storybook/select/select.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { Meta, StoryObj } from "@storybook/react";
import {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectLabel,
SelectItem,
SelectGroup,
} from "../../ui/src/shad/components/ui/select.tsx";

const meta: Meta<typeof Select> = {
component: Select,
render: ({ form, ...args }) => {
return (
<Select>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Fruits</SelectLabel>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
<SelectItem value="blueberry">Blueberry</SelectItem>
<SelectItem value="grapes">Grapes</SelectItem>
<SelectItem value="pineapple">Pineapple</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
);
},
};

export default meta;
type Story = StoryObj<typeof Select>;

export const Primary: Story = {
args: {
value: "Apple",
},
};
5 changes: 3 additions & 2 deletions web/libs/ui/.storybook/preview.scss
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
@import '../src/tokens/colors.scss';
@import '../src/tokens/typography.scss';
@import '../src/tailwind';
@import '../src/tokens/colors';
@import '../src/tokens/typography';
17 changes: 17 additions & 0 deletions web/libs/ui/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "../../tailwind.config.ts",
"css": "./src/tailwind.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@humansignal/shad/components",
"utils": "@humansignal/shad/utils"
}
}
3 changes: 2 additions & 1 deletion web/libs/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
"react": "17.0.2",
"react-dom": "17.0.2"
},
"main": "src/index.ts"
"main": "src/index.ts",
"files": ["src/tailwind.css", "src/shad/components"]
}
46 changes: 46 additions & 0 deletions web/libs/ui/src/shad/components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@humansignal/shad/utils/utils";

const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
},
);
Button.displayName = "Button";

export { Button, buttonVariants };
Loading
Loading