diff --git a/components/Remember.tsx b/components/Remember.tsx index eaa5305ea..55ff3c963 100644 --- a/components/Remember.tsx +++ b/components/Remember.tsx @@ -1,103 +1,106 @@ import React from 'react'; +import { Card, CardHeader, CardContent, CardTitle } from '@/components/ui/card'; +const linkProps = { className: 'underline', rel: 'noreferrer' }; -export const Remember = () => { - return ( -
-

( + + + + + + - - - {' '} Remember -

- - Contribute to the JSON Schema Docs - -
- Code isn't the only way to contribute to OSS; Docs are extremely import - for the JSON Schema Ecosystem. At JSON Schema, We value Docs - contributions as much as every other type of contribution! + + + +
+ + Contribute to the JSON Schema Docs + +

+ Code isn't the only way to contribute to OSS; Docs are extremely + import for the JSON Schema Ecosystem. At JSON Schema, We value Docs + contributions as much as every other type of contribution! +

- - To get started as a Docs contributor: - -
-
    -
  1. + +
    + + To get started as a Docs contributor: + +
      +
    1. Familiarize yourself with our project's{' '} Contribution Guide {' '} and our{' '} Code of Conduct .
    2. -
    3. +
    4. Head over to our{' '} JSON Schema Docs Board .
    5. -
    6. +
    7. Pick an issue you would like to contribute to and leave a comment introducing yourself. This is also the perfect place to leave any questions you may have on how to get started.
    8. -
    9. +
    10. If there is no work done in that Docs issue yet, feel free to open a PR and get started!
    - - Docs contributor questions? - -
    - Do you have a documentation contributor question? Please leave a comment - in the issue or PR or join the #contribute or{' '} - #documentation channels on{' '} - + - Slack - {' '} - and leave a message. + Docs contributor questions? + +

    + Do you have a documentation contributor question? Please leave a + comment in the issue or PR or join the #contribute or{' '} + #documentation channels on{' '} + + Slack + {' '} + and leave a message. +

    -
- ); -}; +
+ +); diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx new file mode 100644 index 000000000..6369ce192 --- /dev/null +++ b/components/ui/badge.tsx @@ -0,0 +1,47 @@ +/* eslint-disable linebreak-style */ +import * as React from 'react'; +import { Slot } from '@radix-ui/react-slot'; +import { cva, type VariantProps } from 'class-variance-authority'; + +import { cn } from '@/lib/utils'; + +const badgeVariants = cva( + 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden', + { + variants: { + variant: { + default: + 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90', + secondary: + 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90', + destructive: + 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', + outline: + 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +); + +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<'span'> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : 'span'; + + return ( + + ); +} + +export { Badge, badgeVariants }; diff --git a/components/ui/card.tsx b/components/ui/card.tsx new file mode 100644 index 000000000..8c45af7d7 --- /dev/null +++ b/components/ui/card.tsx @@ -0,0 +1,151 @@ +/* eslint-disable linebreak-style */ +/* eslint-disable react/prop-types */ +import * as React from 'react'; +import PropTypes from 'prop-types'; + +import { cn } from '@/lib/utils'; + +interface CardProps extends React.HTMLAttributes { + className?: string; +} + +function Card({ className, ...props }: CardProps) { + return ( +
+ ); +} + +Card.propTypes = { + className: PropTypes.string, +}; + +interface CardHeaderProps extends React.HTMLAttributes { + className?: string; +} + +function CardHeader({ className, ...props }: CardHeaderProps) { + return ( +
+ ); +} + +CardHeader.propTypes = { + className: PropTypes.string, +}; + +interface CardTitleProps extends React.HTMLAttributes { + className?: string; +} + +function CardTitle({ className, ...props }: CardTitleProps) { + return ( +
+ ); +} + +CardTitle.propTypes = { + className: PropTypes.string, +}; + +interface CardDescriptionProps extends React.HTMLAttributes { + className?: string; +} + +function CardDescription({ className, ...props }: CardDescriptionProps) { + return ( +
+ ); +} + +CardDescription.propTypes = { + className: PropTypes.string, +}; + +interface CardActionProps extends React.HTMLAttributes { + className?: string; +} + +function CardAction({ className, ...props }: CardActionProps) { + return ( +
+ ); +} + +CardAction.propTypes = { + className: PropTypes.string, +}; + +interface CardContentProps extends React.HTMLAttributes { + className?: string; +} + +function CardContent({ className, ...props }: CardContentProps) { + return ( +
+ ); +} + +CardContent.propTypes = { + className: PropTypes.string, +}; + +interface CardFooterProps extends React.HTMLAttributes { + className?: string; +} + +function CardFooter({ className, ...props }: CardFooterProps) { + return ( +
+ ); +} + +CardFooter.propTypes = { + className: PropTypes.string, +}; + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardAction, + CardDescription, + CardContent, +}; diff --git a/cypress/components/Badge.cy.tsx b/cypress/components/Badge.cy.tsx new file mode 100644 index 000000000..2f9a024fe --- /dev/null +++ b/cypress/components/Badge.cy.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import Badge from '../../pages/tools/components/ui/Badge'; + +describe('Badge Component', () => { + it('renders with correct base classes', () => { + cy.mount(Test Badge); + cy.get('span').should('have.class', 'mx-[4px]'); + cy.get('span').should('have.class', 'my-[4px]'); + cy.get('span').should('have.class', 'bg-[#e2e8f0]'); + cy.get('span').should('have.class', 'dark:bg-[#0f172a]'); + cy.get('span').should('have.class', 'text-[#0f172a]'); + cy.get('span').should('have.class', 'dark:text-white'); + cy.get('span').should('have.class', 'inline-flex'); + cy.get('span').should('have.class', 'items-center'); + cy.get('span').should('have.class', 'justify-center'); + cy.get('span').should('have.class', 'rounded-md'); + cy.get('span').should('have.class', 'border'); + }); + + it('renders with different variants', () => { + cy.mount(Default Badge); + cy.get('span').should('have.class', 'bg-[#e2e8f0]'); + cy.get('span').should('have.class', 'dark:bg-[#0f172a]'); + cy.get('span').should('have.class', 'text-[#0f172a]'); + cy.get('span').should('have.class', 'dark:text-white'); + + cy.mount(Secondary Badge); + cy.get('span').should('have.class', 'bg-[#e2e8f0]'); + cy.get('span').should('have.class', 'dark:bg-[#0f172a]'); + cy.get('span').should('have.class', 'text-[#0f172a]'); + cy.get('span').should('have.class', 'dark:text-white'); + + cy.mount(Outline Badge); + cy.get('span').should('have.class', 'bg-[#e2e8f0]'); + cy.get('span').should('have.class', 'dark:bg-[#0f172a]'); + cy.get('span').should('have.class', 'text-[#0f172a]'); + cy.get('span').should('have.class', 'dark:text-white'); + + cy.mount(Destructive Badge); + cy.get('span').should('have.class', 'bg-[#e2e8f0]'); + cy.get('span').should('have.class', 'dark:bg-[#0f172a]'); + cy.get('span').should('have.class', 'text-[#0f172a]'); + cy.get('span').should('have.class', 'dark:text-white'); + }); + + it('renders children content correctly', () => { + const content = 'Test Badge Content'; + cy.mount({content}); + cy.get('span').should('contain', content); + }); +}); diff --git a/cypress/components/Remember.cy.tsx b/cypress/components/Remember.cy.tsx index 970cdeda7..5766bd7e3 100644 --- a/cypress/components/Remember.cy.tsx +++ b/cypress/components/Remember.cy.tsx @@ -8,10 +8,13 @@ describe('Remember Component', () => { cy.mount(); // Should have the correct elements and text - cy.get('[data-test="remember-heading"]') - .should('have.prop', 'tagName', 'H3') - .and('contain.text', 'Remember'); + .should('have.prop', 'tagName', 'DIV') + .and('contain.text', 'Remember') + .and('have.class', 'text-h5mobile') + .and('have.class', 'md:text-h5') + .and('have.class', 'leading-none') + .and('have.class', 'font-semibold'); cy.get('[data-test="contribute-docs-span"]') .should('have.prop', 'tagName', 'SPAN') diff --git a/cypress/components/Tag.cy.tsx b/cypress/components/Tag.cy.tsx new file mode 100644 index 000000000..166d9ff3b --- /dev/null +++ b/cypress/components/Tag.cy.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import Tag from '../../pages/tools/components/ui/Tag'; + +describe('Tag Component', () => { + it('renders with default neutral intent', () => { + cy.mount(Default Tag); + cy.get('span').should('have.class', 'bg-slate-500/10'); + cy.get('span').should('have.class', 'dark:bg-slate-500/20'); + cy.get('span').should('have.class', 'text-slate-700'); + cy.get('span').should('have.class', 'dark:text-slate-400'); + cy.get('span').should('have.class', 'inline-flex'); + cy.get('span').should('have.class', 'items-center'); + cy.get('span').should('have.class', 'justify-center'); + cy.get('span').should('have.class', 'rounded-md'); + cy.get('span').should('have.class', 'border'); + }); + + it('renders with success intent', () => { + cy.mount(Success Tag); + cy.get('span').should('have.class', 'bg-emerald-500/10'); + cy.get('span').should('have.class', 'dark:bg-emerald-500/20'); + cy.get('span').should('have.class', 'text-emerald-700'); + cy.get('span').should('have.class', 'dark:text-emerald-400'); + }); + + it('renders with warning intent', () => { + cy.mount(Warning Tag); + cy.get('span').should('have.class', 'bg-amber-500/10'); + cy.get('span').should('have.class', 'dark:bg-amber-500/20'); + cy.get('span').should('have.class', 'text-amber-700'); + cy.get('span').should('have.class', 'dark:text-amber-400'); + }); + + it('renders with error intent', () => { + cy.mount(Error Tag); + cy.get('span').should('have.class', 'bg-red-500/10'); + cy.get('span').should('have.class', 'dark:bg-red-500/20'); + cy.get('span').should('have.class', 'text-red-700'); + cy.get('span').should('have.class', 'dark:text-red-400'); + }); + + it('renders children content correctly', () => { + const content = 'Test Tag Content'; + cy.mount({content}); + cy.get('span').should('contain', content); + }); + + it('renders with correct base classes', () => { + cy.mount(Test Tag); + cy.get('span').should('have.class', 'mr-2'); + cy.get('span').should('have.class', 'text-[12px]'); + cy.get('span').should('have.class', 'font-semibold'); + }); +}); diff --git a/cypress/components/ui/card.cy.tsx b/cypress/components/ui/card.cy.tsx new file mode 100644 index 000000000..d9cfb42d6 --- /dev/null +++ b/cypress/components/ui/card.cy.tsx @@ -0,0 +1,36 @@ +// card.cy.tsx + +import React from 'react'; +import { + Card, + CardHeader, + CardFooter, + CardTitle, + CardAction, + CardDescription, + CardContent, +} from '@/components/ui/card'; + +describe('Card Component', () => { + it('renders all Card subcomponents correctly', () => { + cy.mount( + + + Test Title + Action + + Description here + Content goes here + Footer + , + ); + + cy.get('[data-slot="card"]').should('exist'); + cy.get('[data-slot="card-header"]').should('exist'); + cy.get('[data-slot="card-title"]').contains('Test Title'); + cy.get('[data-slot="card-action"]').contains('Action'); + cy.get('[data-slot="card-description"]').contains('Description here'); + cy.get('[data-slot="card-content"]').contains('Content goes here'); + cy.get('[data-slot="card-footer"]').contains('Footer'); + }); +}); diff --git a/package.json b/package.json index 22ab9473e..7fc4ea15b 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "dependencies": { "@docsearch/react": "3.8.0", "@radix-ui/react-checkbox": "^1.3.1", - "@radix-ui/react-slot": "^1.2.2", + "@radix-ui/react-slot": "^1.2.3", "@types/jsonpath": "^0.2.4", "axios": "1.7.7", "babel-loader": "^9.2.1", diff --git a/pages/tools/components/ui/Badge.tsx b/pages/tools/components/ui/Badge.tsx index 2085773c3..961615179 100644 --- a/pages/tools/components/ui/Badge.tsx +++ b/pages/tools/components/ui/Badge.tsx @@ -1,10 +1,19 @@ import React, { ReactNode } from 'react'; +import { Badge as ShadcnBadge } from '../../../../components/ui/badge'; -const Badge = ({ children }: { children: ReactNode }) => { +interface BadgeProps { + children: ReactNode; + variant?: 'default' | 'secondary' | 'outline' | 'destructive'; +} + +const Badge = ({ children, variant = 'secondary' }: BadgeProps) => { return ( -
+ {children} -
+ ); }; diff --git a/pages/tools/components/ui/Tag.tsx b/pages/tools/components/ui/Tag.tsx index 73d31ef2e..541ec6426 100644 --- a/pages/tools/components/ui/Tag.tsx +++ b/pages/tools/components/ui/Tag.tsx @@ -1,5 +1,5 @@ import React, { ReactNode } from 'react'; -import classnames from 'classnames'; +import { Badge as ShadcnBadge } from '../../../../components/ui/badge'; interface TagProps { children: ReactNode; @@ -7,23 +7,31 @@ interface TagProps { } const Tag = ({ children, intent = 'neutral' }: TagProps) => { + const styles = { + success: + 'bg-emerald-500/10 dark:bg-emerald-500/20 text-emerald-700 dark:text-emerald-400', + warning: + 'bg-amber-500/10 dark:bg-amber-500/20 text-amber-700 dark:text-amber-400', + error: 'bg-red-500/10 dark:bg-red-500/20 text-red-700 dark:text-red-400', + neutral: + 'bg-slate-500/10 dark:bg-slate-500/20 text-slate-700 dark:text-slate-400', + } as const; + return ( -
{children} -
+ ); }; diff --git a/yarn.lock b/yarn.lock index 53bb6412d..b434e8767 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3389,7 +3389,7 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-slot@npm:1.2.2, @radix-ui/react-slot@npm:^1.2.2": +"@radix-ui/react-slot@npm:1.2.2": version: 1.2.2 resolution: "@radix-ui/react-slot@npm:1.2.2" dependencies: @@ -3404,6 +3404,21 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-slot@npm:^1.2.3": + version: 1.2.3 + resolution: "@radix-ui/react-slot@npm:1.2.3" + dependencies: + "@radix-ui/react-compose-refs": "npm:1.1.2" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/5913aa0d760f505905779515e4b1f0f71a422350f077cc8d26d1aafe53c97f177fec0e6d7fbbb50d8b5e498aa9df9f707ca75ae3801540c283b26b0136138eef + languageName: node + linkType: hard + "@radix-ui/react-use-controllable-state@npm:1.2.2": version: 1.2.2 resolution: "@radix-ui/react-use-controllable-state@npm:1.2.2" @@ -8534,7 +8549,7 @@ __metadata: "@docsearch/react": "npm:3.8.0" "@next/eslint-plugin-next": "npm:^14.0.1" "@radix-ui/react-checkbox": "npm:^1.3.1" - "@radix-ui/react-slot": "npm:^1.2.2" + "@radix-ui/react-slot": "npm:^1.2.3" "@svgr/webpack": "npm:^8.1.0" "@types/babel__core": "npm:^7" "@types/babel__preset-env": "npm:^7"