From a5290fce8860cd12f8d4e58a433813611058aaaa Mon Sep 17 00:00:00 2001 From: Michael Silva Date: Mon, 9 Jun 2025 12:22:42 -0400 Subject: [PATCH 01/14] moved with eng docs --- .../(app)/settings/root-keys/new/client.tsx | 2 +- apps/dashboard/app/new/keys.tsx | 2 +- .../buttons/visual-button.examples.tsx | 28 +++++++ .../design/components/visual-button.mdx | 75 +++++++++++++++++++ .../ui/src/components}/visible-button.tsx | 7 +- internal/ui/src/index.ts | 1 + 6 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 apps/engineering/content/design/components/buttons/visual-button.examples.tsx create mode 100644 apps/engineering/content/design/components/visual-button.mdx rename {apps/dashboard/components/dashboard => internal/ui/src/components}/visible-button.tsx (81%) diff --git a/apps/dashboard/app/(app)/settings/root-keys/new/client.tsx b/apps/dashboard/app/(app)/settings/root-keys/new/client.tsx index b681ede1556..fdd698dd421 100644 --- a/apps/dashboard/app/(app)/settings/root-keys/new/client.tsx +++ b/apps/dashboard/app/(app)/settings/root-keys/new/client.tsx @@ -1,7 +1,6 @@ "use client"; import { Loading } from "@/components/dashboard/loading"; -import { VisibleButton } from "@/components/dashboard/visible-button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Code } from "@/components/ui/code"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; @@ -17,6 +16,7 @@ import { Label } from "@/components/ui/label"; import { toast } from "@/components/ui/toaster"; import { trpc } from "@/lib/trpc/client"; import { type UnkeyPermission, unkeyPermissionValidation } from "@unkey/rbac"; +import { VisibleButton } from "@unkey/ui"; import { Button, Checkbox, CopyButton, Input } from "@unkey/ui"; import { ChevronRight } from "lucide-react"; import { useRouter } from "next/navigation"; diff --git a/apps/dashboard/app/new/keys.tsx b/apps/dashboard/app/new/keys.tsx index 36ad3cebb35..e41037a7833 100644 --- a/apps/dashboard/app/new/keys.tsx +++ b/apps/dashboard/app/new/keys.tsx @@ -1,7 +1,6 @@ "use client"; import { Loading } from "@/components/dashboard/loading"; -import { VisibleButton } from "@/components/dashboard/visible-button"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Card, @@ -14,6 +13,7 @@ import { import { Code } from "@/components/ui/code"; import { Separator } from "@/components/ui/separator"; import { trpc } from "@/lib/trpc/client"; +import { VisibleButton } from "@unkey/ui"; import { Button, CopyButton, Empty } from "@unkey/ui"; import { AlertCircle, KeyRound, Lock } from "lucide-react"; import Link from "next/link"; diff --git a/apps/engineering/content/design/components/buttons/visual-button.examples.tsx b/apps/engineering/content/design/components/buttons/visual-button.examples.tsx new file mode 100644 index 00000000000..60ee28f62f4 --- /dev/null +++ b/apps/engineering/content/design/components/buttons/visual-button.examples.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { RenderComponentWithSnippet } from "@/app/components/render"; +import { VisibleButton } from "@unkey/ui/src/components/visible-button"; +import { useState } from "react"; + +export function VisibleButtonDemo() { + const [isVisible, setIsVisible] = useState(false); + + return ( + +
+
+ + + {isVisible ? "Content is visible" : "Content is hidden"} + +
+
+ + + {isVisible ? "sk_1234567890abcdef" : "••••••••••••••••"} + +
+
+
+ ); +} diff --git a/apps/engineering/content/design/components/visual-button.mdx b/apps/engineering/content/design/components/visual-button.mdx new file mode 100644 index 00000000000..e47affb5f6e --- /dev/null +++ b/apps/engineering/content/design/components/visual-button.mdx @@ -0,0 +1,75 @@ +--- +title: VisibleButton +summary: A toggle button that switches between visible and hidden states, commonly used for showing/hiding sensitive information. +--- +import { VisibleButtonDemo } from "./buttons/visual-button.examples" + + +# VisibleButton + +The `VisibleButton` component is a toggle button that switches between visible and hidden states, commonly used for showing/hiding sensitive information. It automatically hides content after 10 seconds when visible. + +## Features + +- Toggle between visible/hidden states +- Automatic hiding after 10 seconds +- Accessible with screen readers +- Dark mode support +- Customizable styling through className prop + +## Props + +| Prop | Type | Description | +|------|------|-------------| +| `isVisible` | `boolean` | Current visibility state | +| `setIsVisible` | `(visible: boolean) => void` | Function to update visibility state | +| `className` | `string` | Optional CSS classes to apply to the button | + +Additional props are forwarded to the underlying `button` element. + +## Usage + +```tsx +import { useState } from 'react'; +import { VisibleButton } from '@/components/visible-button'; + +function Example() { + const [isVisible, setIsVisible] = useState(false); + + return ( +
+ + {isVisible ? "Sensitive content" : "•••••••"} +
+ ); +} +``` + +## Examples + + + +## Behavior + +1. Clicking the button toggles between visible (Eye icon) and hidden (EyeOff icon) states +2. When content becomes visible, a 10-second timer starts +3. After 10 seconds, content automatically becomes hidden +4. The timer resets if the content is hidden manually before timeout + +## Accessibility + +- Includes `sr-only` text for screen readers +- Uses semantic button element with proper ARIA attributes +- Maintains focus states for keyboard navigation + +## Design + +The button features: +- A minimal, circular design +- Icon-based state indication (Eye/EyeOff) +- Hover and focus states +- Dark mode support +- Responsive sizing (24x24 pixels) diff --git a/apps/dashboard/components/dashboard/visible-button.tsx b/internal/ui/src/components/visible-button.tsx similarity index 81% rename from apps/dashboard/components/dashboard/visible-button.tsx rename to internal/ui/src/components/visible-button.tsx index ac7d863e12d..245d5415e1b 100644 --- a/apps/dashboard/components/dashboard/visible-button.tsx +++ b/internal/ui/src/components/visible-button.tsx @@ -1,6 +1,9 @@ -import { cn } from "@/lib/utils"; +"use client"; + import { Eye, EyeOff } from "lucide-react"; -import { useEffect } from "react"; +// biome-ignore lint: React in this context is used throughout, so biome will change to types because no APIs are used even though React is needed. +import React, { useEffect } from "react"; +import { cn } from "../lib/utils"; type VisibleButtonProps = React.HTMLAttributes & { isVisible: boolean; diff --git a/internal/ui/src/index.ts b/internal/ui/src/index.ts index 67ba242880f..a85bfff0a0b 100644 --- a/internal/ui/src/index.ts +++ b/internal/ui/src/index.ts @@ -16,3 +16,4 @@ export * from "./components/checkbox"; export * from "./components/timestamp-info"; export * from "./components/tooltip"; export * from "./components/keyboard-button"; +export * from "./components/visible-button"; From 2bdd0b32e57ca836e3e52a10d38318b1292dd44b Mon Sep 17 00:00:00 2001 From: CodeReaper <148160799+MichaelUnkey@users.noreply.github.com> Date: Mon, 9 Jun 2025 12:35:15 -0400 Subject: [PATCH 02/14] Update apps/engineering/content/design/components/buttons/visual-button.examples.tsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../design/components/buttons/visual-button.examples.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/engineering/content/design/components/buttons/visual-button.examples.tsx b/apps/engineering/content/design/components/buttons/visual-button.examples.tsx index 60ee28f62f4..3057a5b0311 100644 --- a/apps/engineering/content/design/components/buttons/visual-button.examples.tsx +++ b/apps/engineering/content/design/components/buttons/visual-button.examples.tsx @@ -1,7 +1,7 @@ "use client"; import { RenderComponentWithSnippet } from "@/app/components/render"; -import { VisibleButton } from "@unkey/ui/src/components/visible-button"; +import { VisibleButton } from "@unkey/ui"; import { useState } from "react"; export function VisibleButtonDemo() { From e388110200623a024219e795e04cf6996e1df2c5 Mon Sep 17 00:00:00 2001 From: CodeReaper <148160799+MichaelUnkey@users.noreply.github.com> Date: Mon, 9 Jun 2025 12:35:29 -0400 Subject: [PATCH 03/14] Update apps/engineering/content/design/components/visual-button.mdx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- apps/engineering/content/design/components/visual-button.mdx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/engineering/content/design/components/visual-button.mdx b/apps/engineering/content/design/components/visual-button.mdx index e47affb5f6e..af015b864d5 100644 --- a/apps/engineering/content/design/components/visual-button.mdx +++ b/apps/engineering/content/design/components/visual-button.mdx @@ -29,9 +29,9 @@ Additional props are forwarded to the underlying `button` element. ## Usage -```tsx import { useState } from 'react'; -import { VisibleButton } from '@/components/visible-button'; +-import { VisibleButton } from '@/components/visible-button'; ++import { VisibleButton } from '@unkey/ui'; function Example() { const [isVisible, setIsVisible] = useState(false); @@ -46,7 +46,6 @@ function Example() { ); } -``` ## Examples From fb84b7b9d5621b6cae65ff18daa5425a90185f3d Mon Sep 17 00:00:00 2001 From: MichaelUnkey Date: Mon, 9 Jun 2025 19:06:55 -0400 Subject: [PATCH 04/14] merge issue --- .../design/components/visual-button.mdx | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/apps/engineering/content/design/components/visual-button.mdx b/apps/engineering/content/design/components/visual-button.mdx index af015b864d5..372750b94a7 100644 --- a/apps/engineering/content/design/components/visual-button.mdx +++ b/apps/engineering/content/design/components/visual-button.mdx @@ -4,11 +4,6 @@ summary: A toggle button that switches between visible and hidden states, common --- import { VisibleButtonDemo } from "./buttons/visual-button.examples" - -# VisibleButton - -The `VisibleButton` component is a toggle button that switches between visible and hidden states, commonly used for showing/hiding sensitive information. It automatically hides content after 10 seconds when visible. - ## Features - Toggle between visible/hidden states @@ -17,21 +12,11 @@ The `VisibleButton` component is a toggle button that switches between visible a - Dark mode support - Customizable styling through className prop -## Props - -| Prop | Type | Description | -|------|------|-------------| -| `isVisible` | `boolean` | Current visibility state | -| `setIsVisible` | `(visible: boolean) => void` | Function to update visibility state | -| `className` | `string` | Optional CSS classes to apply to the button | - -Additional props are forwarded to the underlying `button` element. - ## Usage +```tsx import { useState } from 'react'; --import { VisibleButton } from '@/components/visible-button'; -+import { VisibleButton } from '@unkey/ui'; +import { VisibleButton } from '@unkey/ui'; function Example() { const [isVisible, setIsVisible] = useState(false); @@ -46,11 +31,22 @@ function Example() { ); } +``` ## Examples +## Props + +| Prop | Type | Description | +|------|------|-------------| +| `isVisible` | `boolean` | Current visibility state | +| `setIsVisible` | `(visible: boolean) => void` | Function to update visibility state | +| `className` | `string` | Optional CSS classes to apply to the button | + +Additional props are forwarded to the underlying `button` element. + ## Behavior 1. Clicking the button toggles between visible (Eye icon) and hidden (EyeOff icon) states From 0a4d79bc59ee429d7b1c3fab362b384febca4fdb Mon Sep 17 00:00:00 2001 From: Michael Silva Date: Wed, 11 Jun 2025 13:50:12 -0400 Subject: [PATCH 05/14] icons and eng style stuff --- .../engineering/app/docs/[[...slug]]/page.tsx | 4 +- apps/engineering/app/global.css | 131 +++++++++++++- apps/engineering/app/layout.tsx | 3 +- .../buttons/visual-button.examples.tsx | 14 +- apps/engineering/tailwind.config.js | 163 +++++++++++++++++- internal/icons/src/icons/hide.tsx | 62 +++++++ internal/icons/src/icons/view.tsx | 47 +++++ internal/icons/src/index.ts | 2 + internal/ui/src/components/visible-button.tsx | 24 +-- 9 files changed, 423 insertions(+), 27 deletions(-) create mode 100644 internal/icons/src/icons/hide.tsx create mode 100644 internal/icons/src/icons/view.tsx diff --git a/apps/engineering/app/docs/[[...slug]]/page.tsx b/apps/engineering/app/docs/[[...slug]]/page.tsx index 186f8d7ed4d..01030e99453 100644 --- a/apps/engineering/app/docs/[[...slug]]/page.tsx +++ b/apps/engineering/app/docs/[[...slug]]/page.tsx @@ -15,9 +15,9 @@ export default async function Page(props: { if (!page) { notFound(); - } + } - if (page.slugs.length === 0) { + if (page.slugs.length === 0) { return (
diff --git a/apps/engineering/app/global.css b/apps/engineering/app/global.css index b5c61c95671..92358ab5a96 100644 --- a/apps/engineering/app/global.css +++ b/apps/engineering/app/global.css @@ -1,3 +1,130 @@ @tailwind base; -@tailwind components; -@tailwind utilities; + +@layer base { + :root { + --chart-selection: var(--accent-6); + + --white: 0 0% 100%; + --black: 0 0% 0%; + --background: 60 9% 98%; + --background-subtle: 60 9% 94%; + + --brand: 262 93% 58%; + --brand-foreground: 262 93% 90%; + + --warn: 46, 97%, 65%; + --warn-foreground: 46, 97%, 15%; + + --alert: 0 100% 65%; + --alert-foreground: 0 100% 98%; + + --success: 152, 56%, 39%; + + --primary: 24 10% 10%; + --primary-foreground: 24 10% 90%; + + --secondary: 20 6% 94%; + --secondary-foreground: 0 0% 0%; + + --content: 240 10% 3.9%; + --content-subtle: 240 3.8% 46.1%; + + --content-warn: 46, 97%, 40%; + --content-alert: 0 84.2% 60.2%; + + --border: 24, 6%, 83%; + --ring: 24, 6%, 83%; + } + + .dark { + --chart-selection: var(--accent-9); + + --white: 0 0% 100%; + --black: 0 0% 0%; + + --background: 20 14% 2%; + --background-subtle: 24 10% 10%; + + --brand: 25 95% 53%; + --brand-foreground: 25 95% 90%; + + --warn: 46, 100%, 45%; + --warn-foreground: 46, 97%, 5%; + + --alert: 0 100% 45%; + --alert-foreground: 0 100% 95%; + + --success: 152, 56%, 39%; + + --primary: 20 6% 94%; + --primary-foreground: 20 6% 10%; + + --secondary: 24 10% 10%; + --secondary-foreground: 24 10% 100%; + + --content: 240 10% 90%; + --content-subtle: 240 3.8% 46.1%; + --content-warn: 46, 100%, 55%; + --content-alert: 0 84.2% 60.2%; + + --border: 12, 6%, 15%; + --ring: 12, 6%, 15%; + } +} + +@layer base { + * { + @apply border-border; + } + + html { + @apply scroll-smooth; + } + + body { + @apply bg-background text-content; + } +} + +code { + counter-reset: step; + counter-increment: step 0; + white-space: break-spaces; +} + +code .line::before { + content: counter(step); + counter-increment: step; + width: 1rem; + margin-right: 1.5rem; + display: inline-block; + text-align: right; + color: rgba(115, 138, 148, 0.4); +} + +@keyframes fillLeft { + from { + transform: translateX(-100%); + } + + to { + transform: translateX(0); + } +} + +/* For Webkit-based browsers (Chrome, Safari and Opera) */ +.scrollbar-hide::-webkit-scrollbar { + display: none; +} + +/* For IE, Edge and Firefox */ +.scrollbar-hide { + -ms-overflow-style: none; + /* IE and Edge */ + scrollbar-width: none; + /* Firefox */ +} + +.animate-fill-left { + animation: fillLeft 1s ease-in-out; +} diff --git a/apps/engineering/app/layout.tsx b/apps/engineering/app/layout.tsx index 567c63a1aae..208fae9c9f3 100644 --- a/apps/engineering/app/layout.tsx +++ b/apps/engineering/app/layout.tsx @@ -4,8 +4,9 @@ import { GeistSans } from "geist/font/sans"; import type { ReactNode } from "react"; -import { TooltipProvider } from "@unkey/ui/src/components/tooltip"; import "./global.css"; +import "@unkey/ui/css"; +import { TooltipProvider } from "@unkey/ui/src/components/tooltip"; export default function Layout({ children }: { children: ReactNode }) { return ( diff --git a/apps/engineering/content/design/components/buttons/visual-button.examples.tsx b/apps/engineering/content/design/components/buttons/visual-button.examples.tsx index 3057a5b0311..6c1ebf97d13 100644 --- a/apps/engineering/content/design/components/buttons/visual-button.examples.tsx +++ b/apps/engineering/content/design/components/buttons/visual-button.examples.tsx @@ -10,17 +10,17 @@ export function VisibleButtonDemo() { return (
-
- - +
+ {isVisible ? "Content is visible" : "Content is hidden"} - + +
-
- - +
+ {isVisible ? "sk_1234567890abcdef" : "••••••••••••••••"} +
diff --git a/apps/engineering/tailwind.config.js b/apps/engineering/tailwind.config.js index 9fa5dd1d5e0..35782308338 100644 --- a/apps/engineering/tailwind.config.js +++ b/apps/engineering/tailwind.config.js @@ -1,7 +1,8 @@ -import unkeyUiTailwindConfig from "@unkey/ui/tailwind.config"; +import defaultTheme from "@unkey/ui/tailwind.config"; import { createPreset } from "fumadocs-ui/tailwind-plugin"; /** @type {import('tailwindcss').Config} */ -export default { +module.exports = { + darkMode: ["class"], content: [ "./components/**/*.{ts,tsx}", "./app/**/*.{ts,tsx}", @@ -11,7 +12,163 @@ export default { "../../internal/ui/src/**/*.tsx", "../../internal/icons/src/**/*.tsx", ], + theme: merge(defaultTheme.theme, { + /** + * We need to remove almost all of these and move them into `@unkey/ui`. + * Especially colors and font sizes need to go + */ + fontSize: { + xxs: ["10px", "16px"], + xs: ["0.75rem", { lineHeight: "1rem" }], + sm: ["0.875rem", { lineHeight: "1.5rem" }], + base: ["1rem", { lineHeight: "1.75rem" }], + lg: ["1.125rem", { lineHeight: "1.75rem" }], + xl: ["1.25rem", { lineHeight: "2rem" }], + "2xl": ["1.5rem", { lineHeight: "2.25rem" }], + "3xl": ["1.75rem", { lineHeight: "2.25rem" }], + "4xl": ["2rem", { lineHeight: "2.5rem" }], + "5xl": ["2.5rem", { lineHeight: "3rem" }], + "6xl": ["3rem", { lineHeight: "3.5rem" }], + "7xl": ["4rem", { lineHeight: "4.5rem" }], + }, + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, - theme: unkeyUiTailwindConfig.theme, + colors: { + current: "currentColor", + transparent: "transparent", + white: "hsl(var(--white))", + black: "hsl(var(--black))", + gray: { + 50: "#fafaf9", + 100: "#f5f5f4", + 200: "#e7e5e4", + 300: "#d6d3d1", + 400: "#a8a29e", + 500: "#78716c", + 600: "#57534e", + 700: "#44403c", + 800: "#292524", + 900: "#1c1917", + 950: "#0c0a09", + }, + background: { + DEFAULT: "hsl(var(--background))", + subtle: "hsl(var(--background-subtle))", + }, + content: { + DEFAULT: "hsl(var(--content))", + subtle: "hsl(var(--content-subtle))", + info: "hsl(var(--content-info))", + warn: "hsl(var(--content-warn))", + alert: "hsl(var(--content-alert))", + }, + + brand: { + DEFAULT: "hsl(var(--brand))", + foreground: "hsl(var(--brand-foreground))", + }, + + warn: { + DEFAULT: "hsl(var(--warn))", + foreground: "hsl(var(--warn-foreground))", + }, + + alert: { + DEFAULT: "hsl(var(--alert))", + foreground: "hsl(var(--alert-foreground))", + }, + success: { + DEFAULT: "hsl(var(--success))", + }, + + "amber-2": "var(--amber-2)", + "amber-3": "var(--amber-3)", + "amber-4": "var(--amber-4)", + "amber-6": "var(--amber-6)", + "amber-11": "var(--amber-11)", + + "red-2": "var(--red-2)", + "red-3": "var(--red-3)", + "red-4": "var(--red-4)", + "red-6": "var(--red-6)", + "red-11": "var(--red-11)", + + subtle: { + DEFAULT: "hsl(var(--subtle))", + foreground: "hsl(var(--subtle-foreground))", + }, + + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + + border: "hsl(var(--border))", + ring: "hsl(var(--ring))", + }, + extend: { + keyframes: { + "accordion-down": { + from: { height: 0 }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: 0 }, + }, + "shiny-text": { + "0%, 90%, 100%": { + "background-position": "calc(-100% - var(--shiny-width)) 0", + }, + "30%, 60%": { + "background-position": "calc(100% + var(--shiny-width)) 0", + }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", + "shiny-text": "shiny-text 10s infinite", + }, + fontFamily: { + sans: ["var(--font-geist-sans)"], + mono: ["var(--font-geist-mono)"], + }, + }, + }), + plugins: [ + require("tailwindcss-animate"), + require("@tailwindcss/typography"), + require("@tailwindcss/aspect-ratio"), + require("@tailwindcss/container-queries"), + ], presets: [createPreset()], }; + +export function merge(obj1, obj2) { + for (const key in obj2) { + // biome-ignore lint/suspicious/noPrototypeBuiltins: don't tell me what to do + if (obj2.hasOwnProperty(key)) { + if (typeof obj2[key] === "object" && !Array.isArray(obj2[key])) { + if (!obj1[key]) { + obj1[key] = {}; + } + obj1[key] = merge(obj1[key], obj2[key]); + } else { + obj1[key] = obj2[key]; + } + } + } + return obj1; +} diff --git a/internal/icons/src/icons/hide.tsx b/internal/icons/src/icons/hide.tsx new file mode 100644 index 00000000000..3268c3dbd20 --- /dev/null +++ b/internal/icons/src/icons/hide.tsx @@ -0,0 +1,62 @@ +/** + * Copyright © Nucleo + * Version 1.3, January 3, 2024 + * Nucleo Icons + * https://nucleoapp.com/ + * - Redistribution of icons is prohibited. + * - Icons are restricted for use only within the product they are bundled with. + * + * For more details: + * https://nucleoapp.com/license + */ +import type React from "react"; + +import { type IconProps, sizeMap } from "../props"; + +export const Hide: React.FC = ({ size = "xl-thin", filled, ...props }) => { + const { size: pixelSize, strokeWidth } = sizeMap[size]; + + return ( + + + + + + + + + ); +}; diff --git a/internal/icons/src/icons/view.tsx b/internal/icons/src/icons/view.tsx new file mode 100644 index 00000000000..e5a35c31f6d --- /dev/null +++ b/internal/icons/src/icons/view.tsx @@ -0,0 +1,47 @@ +/** + * Copyright © Nucleo + * Version 1.3, January 3, 2024 + * Nucleo Icons + * https://nucleoapp.com/ + * - Redistribution of icons is prohibited. + * - Icons are restricted for use only within the product they are bundled with. + * + * For more details: + * https://nucleoapp.com/license + */ +import type React from "react"; + +import { type IconProps, sizeMap } from "../props"; + +export const View: React.FC = ({ size = "xl-thin", filled, ...props }) => { + const { size: pixelSize, strokeWidth } = sizeMap[size]; + + return ( + + + + + + + ); +}; diff --git a/internal/icons/src/index.ts b/internal/icons/src/index.ts index b236dd44aa7..1bcbfd7fa47 100644 --- a/internal/icons/src/index.ts +++ b/internal/icons/src/index.ts @@ -55,6 +55,7 @@ export * from "./icons/folder-cloud"; export * from "./icons/gauge"; export * from "./icons/gear"; export * from "./icons/grid"; +export * from "./icons/hide"; export * from "./icons/input-password-edit"; export * from "./icons/input-password-settings"; export * from "./icons/input-search"; @@ -94,5 +95,6 @@ export * from "./icons/triangle-warning-2"; export * from "./icons/ufo"; export * from "./icons/user-search"; export * from "./icons/user"; +export * from "./icons/view"; export * from "./icons/xmark"; export * from "./icons/coins"; diff --git a/internal/ui/src/components/visible-button.tsx b/internal/ui/src/components/visible-button.tsx index 245d5415e1b..66a6c96a38b 100644 --- a/internal/ui/src/components/visible-button.tsx +++ b/internal/ui/src/components/visible-button.tsx @@ -1,11 +1,11 @@ "use client"; -import { Eye, EyeOff } from "lucide-react"; +// TODO: Convert to Nucleo Icons, Add them to unkey/icons +import { Hide, View } from "@unkey/icons"; // biome-ignore lint: React in this context is used throughout, so biome will change to types because no APIs are used even though React is needed. import React, { useEffect } from "react"; -import { cn } from "../lib/utils"; - -type VisibleButtonProps = React.HTMLAttributes & { +import { Button, type ButtonProps } from "./button"; +type VisibleButtonProps = ButtonProps & { isVisible: boolean; setIsVisible: (visible: boolean) => void; }; @@ -27,19 +27,19 @@ export function VisibleButton({ }, [setIsVisible, isVisible]); return ( - + {isVisible ? : } + ); } From 91b3135805b0f1bd250fae2357a91ff46f61760c Mon Sep 17 00:00:00 2001 From: Michael Silva Date: Thu, 12 Jun 2025 15:35:50 -0400 Subject: [PATCH 06/14] small changes --- .../components/key-created-success-dialog.tsx | 17 +++-- .../create-key/components/secret-key.tsx | 25 +++----- apps/dashboard/app/new/keys.tsx | 1 - internal/icons/src/icons/hide.tsx | 62 ------------------- internal/icons/src/icons/view.tsx | 47 -------------- internal/icons/src/index.ts | 2 - internal/ui/src/components/visible-button.tsx | 43 ++++--------- 7 files changed, 26 insertions(+), 171 deletions(-) delete mode 100644 internal/icons/src/icons/hide.tsx delete mode 100644 internal/icons/src/icons/view.tsx diff --git a/apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/key-created-success-dialog.tsx b/apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/key-created-success-dialog.tsx index 869b10ff64d..7db8649d064 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/key-created-success-dialog.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/key-created-success-dialog.tsx @@ -3,8 +3,8 @@ import { ConfirmPopover } from "@/components/confirmation-popover"; import { Dialog, DialogContent } from "@/components/ui/dialog"; import { toast } from "@/components/ui/toaster"; -import { ArrowRight, Check, CircleInfo, Eye, EyeSlash, Key2, Plus } from "@unkey/icons"; -import { Button, CopyButton, InfoTooltip } from "@unkey/ui"; +import { ArrowRight, Check, CircleInfo, Key2, Plus } from "@unkey/icons"; +import { Button, CopyButton, InfoTooltip, VisibleButton } from "@unkey/ui"; import { useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { UNNAMED_KEY } from "../create-key.constants"; @@ -228,14 +228,11 @@ export const KeyCreatedSuccessDialog = ({
- + setShowKeyInSnippet(visible)} + title="Key Snippet" + /> - + setIsVisible(visible)} + title={title} + /> ); } From 2c45100dc71ce7a866f8fce27bd76286ec90d6a9 Mon Sep 17 00:00:00 2001 From: Michael Silva Date: Fri, 13 Jun 2025 10:11:14 -0400 Subject: [PATCH 07/14] fix eng colors and modify component --- .../(app)/settings/root-keys/new/client.tsx | 14 +++++++++-- apps/dashboard/app/new/keys.tsx | 23 ++++++++++++++++--- .../buttons/visual-button.examples.tsx | 15 ++++++++---- .../design/components/visual-button.mdx | 23 +++++++++++-------- internal/ui/src/components/visible-button.tsx | 10 ++++---- 5 files changed, 62 insertions(+), 23 deletions(-) diff --git a/apps/dashboard/app/(app)/settings/root-keys/new/client.tsx b/apps/dashboard/app/(app)/settings/root-keys/new/client.tsx index 1d5d8cfe751..47406889782 100644 --- a/apps/dashboard/app/(app)/settings/root-keys/new/client.tsx +++ b/apps/dashboard/app/(app)/settings/root-keys/new/client.tsx @@ -283,7 +283,12 @@ export const Client: React.FC = ({ apis }) => { {showKey ? key.data?.key : maskedKey}
- +
@@ -294,7 +299,12 @@ export const Client: React.FC = ({ apis }) => {

- +
diff --git a/apps/dashboard/app/new/keys.tsx b/apps/dashboard/app/new/keys.tsx index 7bca9842d8f..dab7285f430 100644 --- a/apps/dashboard/app/new/keys.tsx +++ b/apps/dashboard/app/new/keys.tsx @@ -145,7 +145,12 @@ export const Keys: React.FC = ({ keyAuthId, apiId }) => { {showKey ? step.rootKey : maskKey(step.rootKey)}
- +
@@ -163,7 +168,12 @@ export const Keys: React.FC = ({ keyAuthId, apiId }) => { : createKeySnippet.replace(step.rootKey, maskKey(step.rootKey))}
- +
@@ -198,7 +208,12 @@ export const Keys: React.FC = ({ keyAuthId, apiId }) => { {showKey ? step.key : maskKey(step.key)}
- +
@@ -217,6 +232,8 @@ export const Keys: React.FC = ({ keyAuthId, apiId }) => { ) : null} diff --git a/apps/engineering/content/design/components/buttons/visual-button.examples.tsx b/apps/engineering/content/design/components/buttons/visual-button.examples.tsx index 6c1ebf97d13..846d58cd83c 100644 --- a/apps/engineering/content/design/components/buttons/visual-button.examples.tsx +++ b/apps/engineering/content/design/components/buttons/visual-button.examples.tsx @@ -10,17 +10,24 @@ export function VisibleButtonDemo() { return (
-
- +
Default Variant
+
+ {isVisible ? "Content is visible" : "Content is hidden"}
-
+
Ghost Variant
+
{isVisible ? "sk_1234567890abcdef" : "••••••••••••••••"} - +
diff --git a/apps/engineering/content/design/components/visual-button.mdx b/apps/engineering/content/design/components/visual-button.mdx index 372750b94a7..71c17de22b6 100644 --- a/apps/engineering/content/design/components/visual-button.mdx +++ b/apps/engineering/content/design/components/visual-button.mdx @@ -7,10 +7,10 @@ import { VisibleButtonDemo } from "./buttons/visual-button.examples" ## Features - Toggle between visible/hidden states -- Automatic hiding after 10 seconds - Accessible with screen readers - Dark mode support - Customizable styling through className prop +- Configurable button variant ## Usage @@ -25,7 +25,9 @@ function Example() {
{isVisible ? "Sensitive content" : "•••••••"}
@@ -44,27 +46,28 @@ function Example() { | `isVisible` | `boolean` | Current visibility state | | `setIsVisible` | `(visible: boolean) => void` | Function to update visibility state | | `className` | `string` | Optional CSS classes to apply to the button | +| `title` | `string` | Text used for accessibility labels and tooltip | +| `variant` | `ButtonProps["variant"]` | Optional button variant (defaults to "outline") | -Additional props are forwarded to the underlying `button` element. +Additional props are forwarded to the underlying `Button` component. ## Behavior -1. Clicking the button toggles between visible (Eye icon) and hidden (EyeOff icon) states -2. When content becomes visible, a 10-second timer starts -3. After 10 seconds, content automatically becomes hidden -4. The timer resets if the content is hidden manually before timeout +1. Clicking the button toggles between visible (Eye icon) and hidden (EyeSlash icon) states +2. The button's aria-label and title attributes update based on the current state and provided title prop ## Accessibility -- Includes `sr-only` text for screen readers +- Includes dynamic aria-labels based on the title prop - Uses semantic button element with proper ARIA attributes - Maintains focus states for keyboard navigation +- Shows tooltip on hover with current action ## Design The button features: - A minimal, circular design -- Icon-based state indication (Eye/EyeOff) +- Icon-based state indication (Eye/EyeSlash) - Hover and focus states - Dark mode support -- Responsive sizing (24x24 pixels) +- Responsive sizing (inherits from Button component) diff --git a/internal/ui/src/components/visible-button.tsx b/internal/ui/src/components/visible-button.tsx index a120b2705f2..c37fd21552e 100644 --- a/internal/ui/src/components/visible-button.tsx +++ b/internal/ui/src/components/visible-button.tsx @@ -1,21 +1,23 @@ "use client"; import { Eye, EyeSlash } from "@unkey/icons"; -import { cn } from "../lib/utils"; +// biome-ignore lint: React in this context is used throughout, so biome will change to types because no APIs are used even though React is needed. +import * as React from "react"; import { Button, type ButtonProps } from "./button"; type VisibleButtonProps = ButtonProps & { isVisible: boolean; setIsVisible: (visible: boolean) => void; + variant?: ButtonProps["variant"]; }; -export function VisibleButton({ isVisible, setIsVisible, ...props }: VisibleButtonProps) { +export function VisibleButton({ isVisible, setIsVisible, variant, ...props }: VisibleButtonProps) { const { title, className, ...rest } = props; return (
Ghost Variant
@@ -27,6 +27,8 @@ export function VisibleButtonDemo() { setIsVisible={setIsVisible} className="right-1 focus:ring-0" variant="ghost" + title="Key" + />
diff --git a/internal/ui/src/components/visible-button.tsx b/internal/ui/src/components/visible-button.tsx index c37fd21552e..45ca781a439 100644 --- a/internal/ui/src/components/visible-button.tsx +++ b/internal/ui/src/components/visible-button.tsx @@ -3,24 +3,38 @@ import { Eye, EyeSlash } from "@unkey/icons"; // biome-ignore lint: React in this context is used throughout, so biome will change to types because no APIs are used even though React is needed. import * as React from "react"; import { Button, type ButtonProps } from "./button"; +import { cn } from "../lib/utils"; type VisibleButtonProps = ButtonProps & { isVisible: boolean; setIsVisible: (visible: boolean) => void; variant?: ButtonProps["variant"]; + size?: ButtonProps["size"]; }; -export function VisibleButton({ isVisible, setIsVisible, variant, ...props }: VisibleButtonProps) { - const { title, className, ...rest } = props; +export function VisibleButton({ + isVisible, + setIsVisible, + variant = "outline", + title, + size = "icon", + onClick, + className, + ...rest +}: VisibleButtonProps) { return ( From 532e3fd2c21ce451124b05b7369a13a50a8a0392 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 17:30:57 +0000 Subject: [PATCH 10/14] [autofix.ci] apply automated fixes --- .../app/(app)/settings/root-keys/new/client.tsx | 2 +- apps/dashboard/app/new/keys.tsx | 2 +- .../components/buttons/visual-button.examples.tsx | 9 +++++++-- internal/ui/src/components/visible-button.tsx | 10 +++++----- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/apps/dashboard/app/(app)/settings/root-keys/new/client.tsx b/apps/dashboard/app/(app)/settings/root-keys/new/client.tsx index aa079e15cb0..6012d259269 100644 --- a/apps/dashboard/app/(app)/settings/root-keys/new/client.tsx +++ b/apps/dashboard/app/(app)/settings/root-keys/new/client.tsx @@ -24,7 +24,7 @@ import { Checkbox, CopyButton, Input, - VisibleButton + VisibleButton, } from "@unkey/ui"; import { ChevronRight } from "lucide-react"; import { useRouter } from "next/navigation"; diff --git a/apps/dashboard/app/new/keys.tsx b/apps/dashboard/app/new/keys.tsx index af2b329900b..f15cebccd3b 100644 --- a/apps/dashboard/app/new/keys.tsx +++ b/apps/dashboard/app/new/keys.tsx @@ -14,7 +14,7 @@ import { CopyButton, Empty, Separator, - VisibleButton + VisibleButton, } from "@unkey/ui"; import { AlertCircle, KeyRound, Lock } from "lucide-react"; import Link from "next/link"; diff --git a/apps/engineering/content/design/components/buttons/visual-button.examples.tsx b/apps/engineering/content/design/components/buttons/visual-button.examples.tsx index 6327d880ac1..74d22f86c8f 100644 --- a/apps/engineering/content/design/components/buttons/visual-button.examples.tsx +++ b/apps/engineering/content/design/components/buttons/visual-button.examples.tsx @@ -15,7 +15,13 @@ export function VisibleButtonDemo() { {isVisible ? "Content is visible" : "Content is hidden"} - +
Ghost Variant
@@ -28,7 +34,6 @@ export function VisibleButtonDemo() { className="right-1 focus:ring-0" variant="ghost" title="Key" - />
diff --git a/internal/ui/src/components/visible-button.tsx b/internal/ui/src/components/visible-button.tsx index 45ca781a439..5ea35a3eb5a 100644 --- a/internal/ui/src/components/visible-button.tsx +++ b/internal/ui/src/components/visible-button.tsx @@ -2,8 +2,8 @@ import { Eye, EyeSlash } from "@unkey/icons"; // biome-ignore lint: React in this context is used throughout, so biome will change to types because no APIs are used even though React is needed. import * as React from "react"; -import { Button, type ButtonProps } from "./button"; import { cn } from "../lib/utils"; +import { Button, type ButtonProps } from "./button"; type VisibleButtonProps = ButtonProps & { isVisible: boolean; @@ -12,15 +12,15 @@ type VisibleButtonProps = ButtonProps & { size?: ButtonProps["size"]; }; -export function VisibleButton({ +export function VisibleButton({ isVisible, - setIsVisible, + setIsVisible, variant = "outline", title, size = "icon", onClick, className, - ...rest + ...rest }: VisibleButtonProps) { return (