-
Notifications
You must be signed in to change notification settings - Fork 116
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
Create blockquote #4168
base: main
Are you sure you want to change the base?
Create blockquote #4168
Changes from all commits
fd7f841
f4a7024
5221338
d145b7b
47de51a
db38b49
569d55a
5fcbcb2
e392100
8809105
c48695b
97a3257
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@twilio-paste/codemods": minor | ||
--- | ||
|
||
[Blockquote]: Add new component |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@twilio-paste/icons": minor | ||
--- | ||
|
||
[Icon]: Add Blockquote icon. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
"@twilio-paste/box": minor | ||
"@twilio-paste/core": minor | ||
--- | ||
|
||
[Box]: Add cite prop for use with Blockquote |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
"@twilio-paste/blockquote": major | ||
"@twilio-paste/core": major | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue(blocking): minor for core |
||
--- | ||
|
||
[Blockquote]: Added a new Blockquote component to library to act as a stylized text wrapper for a quotation and source. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. such a nitpick but did this happen automatically from the plop template? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 Should be removed as t would show up on the docs page for the component |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import { render, screen } from "@testing-library/react"; | ||
import * as React from "react"; | ||
|
||
import { Blockquote, BlockquoteContent, BlockquoteSource } from "../src"; | ||
|
||
describe("Blockquote", () => { | ||
it("should render", () => { | ||
render( | ||
<Blockquote url="#" data-testid="blockquote"> | ||
<BlockquoteContent>This is some text.</BlockquoteContent> | ||
<BlockquoteSource author="Google" source="People + AI Guidebook" /> | ||
</Blockquote>, | ||
); | ||
|
||
const blockquote = screen.getByTestId("blockquote"); | ||
expect(blockquote).toBeDefined(); | ||
|
||
const text = screen.getByText("This is some text."); | ||
expect(text.nodeName).toBe("BLOCKQUOTE"); | ||
expect(blockquote.querySelector(`[data-paste-element='BLOCKQUOTE_SOURCE_ANCHOR']`)).toBeTruthy(); | ||
}); | ||
|
||
it("should render without a url", () => { | ||
render( | ||
<Blockquote data-testid="blockquote"> | ||
<BlockquoteContent>This is some text.</BlockquoteContent> | ||
<BlockquoteSource author="Google" source="People + AI Guidebook" /> | ||
</Blockquote>, | ||
); | ||
|
||
const blockquote = screen.getByTestId("blockquote"); | ||
expect(blockquote).toBeDefined(); | ||
expect(blockquote.querySelector(`[data-paste-element='BLOCKQUOTE_SOURCE_TEXT']`)).toBeTruthy(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no test to be sure |
||
}); | ||
|
||
it("should render without a source", () => { | ||
render( | ||
<Blockquote url="#" data-testid="blockquote"> | ||
<BlockquoteContent>This is some text.</BlockquoteContent> | ||
<BlockquoteSource author="Google" /> | ||
</Blockquote>, | ||
); | ||
|
||
const blockquote = screen.getByTestId("blockquote"); | ||
expect(blockquote).toBeDefined(); | ||
expect(blockquote.querySelector(`[data-paste-element='BLOCKQUOTE_SOURCE_CITE']`)).toBeFalsy(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❤️ like this |
||
}); | ||
}); | ||
|
||
describe("Customization", () => { | ||
it("should set element data attribute", () => { | ||
const { getByTestId } = render( | ||
<Blockquote url="#" data-testid="blockquote"> | ||
<BlockquoteContent>This is some text.</BlockquoteContent> | ||
<BlockquoteSource author="Google" source="People + AI Guidebook" /> | ||
</Blockquote>, | ||
); | ||
const { getByTestId: idWithoutURL } = render( | ||
<Blockquote data-testid="blockquoteWithoutURL"> | ||
<BlockquoteContent>This is some text.</BlockquoteContent> | ||
<BlockquoteSource author="Google" source="People + AI Guidebook" /> | ||
</Blockquote>, | ||
); | ||
|
||
expect(getByTestId("blockquote").getAttribute("data-paste-element")).toEqual("BLOCKQUOTE"); | ||
expect(getByTestId("blockquote").querySelector(`[data-paste-element='BLOCKQUOTE_ICON']`)).toBeTruthy(); | ||
expect(getByTestId("blockquote").querySelector(`[data-paste-element='BLOCKQUOTE_CONTENT']`)).toBeTruthy(); | ||
expect(getByTestId("blockquote").querySelector(`[data-paste-element='BLOCKQUOTE_SOURCE']`)).toBeTruthy(); | ||
expect(getByTestId("blockquote").querySelector(`[data-paste-element='BLOCKQUOTE_SOURCE_AUTHOR']`)).toBeTruthy(); | ||
expect(getByTestId("blockquote").querySelector(`[data-paste-element='BLOCKQUOTE_SOURCE_CITE']`)).toBeTruthy(); | ||
expect(getByTestId("blockquote").querySelector(`[data-paste-element='BLOCKQUOTE_SOURCE_ANCHOR']`)).toBeTruthy(); | ||
expect( | ||
idWithoutURL("blockquoteWithoutURL").querySelector(`[data-paste-element='BLOCKQUOTE_SOURCE_TEXT']`), | ||
).toBeTruthy(); | ||
}); | ||
|
||
it("should set custom element data attribute", () => { | ||
const { getByTestId } = render( | ||
<Blockquote url="#" data-testid="blockquote" element="CUSTOMIZED"> | ||
<BlockquoteContent element="CUSTOMIZED_CONTENT">This is some text.</BlockquoteContent> | ||
<BlockquoteSource author="Google" source="People + AI Guidebook" element="CUSTOMIZED_SOURCE" /> | ||
</Blockquote>, | ||
); | ||
const { getByTestId: idWithoutURL } = render( | ||
<Blockquote data-testid="blockquoteWithoutURL" element="CUSTOMIZED"> | ||
<BlockquoteContent element="CUSTOMIZED_CONTENT">This is some text.</BlockquoteContent> | ||
<BlockquoteSource author="Google" source="People + AI Guidebook" element="CUSTOMIZED_SOURCE" /> | ||
</Blockquote>, | ||
); | ||
|
||
expect(getByTestId("blockquote").getAttribute("data-paste-element")).toEqual("CUSTOMIZED"); | ||
expect(getByTestId("blockquote").querySelector(`[data-paste-element='CUSTOMIZED_ICON']`)).toBeTruthy(); | ||
expect(getByTestId("blockquote").querySelector(`[data-paste-element='CUSTOMIZED_CONTENT']`)).toBeTruthy(); | ||
expect(getByTestId("blockquote").querySelector(`[data-paste-element='CUSTOMIZED_SOURCE']`)).toBeTruthy(); | ||
expect(getByTestId("blockquote").querySelector(`[data-paste-element='CUSTOMIZED_SOURCE_AUTHOR']`)).toBeTruthy(); | ||
expect(getByTestId("blockquote").querySelector(`[data-paste-element='CUSTOMIZED_SOURCE_CITE']`)).toBeTruthy(); | ||
expect(getByTestId("blockquote").querySelector(`[data-paste-element='CUSTOMIZED_SOURCE_ANCHOR']`)).toBeTruthy(); | ||
expect( | ||
idWithoutURL("blockquoteWithoutURL").querySelector(`[data-paste-element='CUSTOMIZED_SOURCE_TEXT']`), | ||
).toBeTruthy(); | ||
}); | ||
Comment on lines
+51
to
+101
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: these tests can be combined |
||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
const { build } = require("../../../../tools/build/esbuild"); | ||
|
||
build(require("./package.json")); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
{ | ||
"name": "@twilio-paste/blockquote", | ||
"version": "0.0.0", | ||
"category": "layout", | ||
"status": "production", | ||
"description": "A Blockquote is a stylized text wrapper for a quotation and source.", | ||
"author": "Twilio Inc.", | ||
"license": "MIT", | ||
"main:dev": "src/index.tsx", | ||
"main": "dist/index.js", | ||
"module": "dist/index.es.js", | ||
"types": "dist/index.d.ts", | ||
"sideEffects": false, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"files": [ | ||
"dist" | ||
], | ||
"scripts": { | ||
"build": "yarn clean && NODE_ENV=production node build.js && tsc", | ||
"build:js": "NODE_ENV=development node build.js", | ||
"build:typedocs": "tsx ../../../../tools/build/generate-type-docs", | ||
"clean": "rm -rf ./dist", | ||
"tsc": "tsc" | ||
}, | ||
"peerDependencies": { | ||
"@twilio-paste/anchor": "^12.0.0", | ||
"@twilio-paste/animation-library": "^2.0.0", | ||
"@twilio-paste/box": "^10.0.0", | ||
"@twilio-paste/button": "^14.0.0", | ||
"@twilio-paste/color-contrast-utils": "^5.0.0", | ||
"@twilio-paste/customization": "^8.0.0", | ||
"@twilio-paste/design-tokens": "^10.0.0", | ||
"@twilio-paste/icons": "^12.0.0", | ||
"@twilio-paste/screen-reader-only": "^13.0.0", | ||
"@twilio-paste/spinner": "^14.0.0", | ||
"@twilio-paste/stack": "^8.0.0", | ||
"@twilio-paste/style-props": "^9.0.0", | ||
"@twilio-paste/styling-library": "^3.0.0", | ||
"@twilio-paste/text": "^10.0.0", | ||
"@twilio-paste/theme": "^11.0.0", | ||
"@twilio-paste/types": "^6.0.0", | ||
"@twilio-paste/uid-library": "^2.0.0", | ||
"@types/react": "^16.8.6 || ^17.0.2 || ^18.0.27", | ||
"@types/react-dom": "^16.8.6 || ^17.0.2 || ^18.0.10", | ||
"react": "^16.8.6 || ^17.0.2 || ^18.0.0", | ||
"react-dom": "^16.8.6 || ^17.0.2 || ^18.0.0" | ||
}, | ||
"devDependencies": { | ||
"@twilio-paste/anchor": "^12.0.0", | ||
"@twilio-paste/animation-library": "^2.0.0", | ||
"@twilio-paste/box": "^10.1.0", | ||
"@twilio-paste/button": "^14.1.0", | ||
"@twilio-paste/color-contrast-utils": "^5.0.0", | ||
"@twilio-paste/customization": "^8.1.0", | ||
"@twilio-paste/design-tokens": "^10.9.0", | ||
"@twilio-paste/icons": "^12.2.0", | ||
"@twilio-paste/screen-reader-only": "^13.1.0", | ||
"@twilio-paste/spinner": "^14.0.0", | ||
"@twilio-paste/stack": "^8.0.0", | ||
"@twilio-paste/style-props": "^9.1.0", | ||
"@twilio-paste/styling-library": "^3.0.0", | ||
"@twilio-paste/text": "^10.1.0", | ||
"@twilio-paste/theme": "^11.0.0", | ||
"@twilio-paste/types": "^6.0.0", | ||
"@twilio-paste/uid-library": "^2.0.0", | ||
"@types/react": "^18.0.27", | ||
"@types/react-dom": "^18.0.10", | ||
"react": "^18.0.0", | ||
"react-dom": "^18.0.0", | ||
"tsx": "^4.0.0", | ||
"typescript": "^4.9.4" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { Box, type BoxProps } from "@twilio-paste/box"; | ||
import { BlockquoteIcon } from "@twilio-paste/icons/esm/BlockquoteIcon"; | ||
import type { HTMLPasteProps } from "@twilio-paste/types"; | ||
import React from "react"; | ||
|
||
import { BlockquoteContext } from "./BlockquoteContext"; | ||
|
||
export interface BlockquoteProps extends HTMLPasteProps<"div"> { | ||
children?: React.ReactNode; | ||
/** | ||
* Overrides the default element name to apply unique styles with the Customization Provider | ||
* @default 'BLOCKQUOTE' | ||
* @type {BoxProps['element']} | ||
* @memberof BlockquoteProps | ||
*/ | ||
element?: BoxProps["element"]; | ||
/** | ||
* The URL to the source of the quote | ||
* @type {string} | ||
* @memberof BlockquoteProps | ||
*/ | ||
url?: string; | ||
} | ||
|
||
export const Blockquote = React.forwardRef<HTMLDivElement, BlockquoteProps>( | ||
({ children, element = "BLOCKQUOTE", url, ...props }, ref) => { | ||
return ( | ||
<Box | ||
{...props} | ||
ref={ref} | ||
display="flex" | ||
columnGap="space50" | ||
alignItems="flex-start" | ||
lineHeight="lineHeight30" | ||
fontSize="fontSize30" | ||
element={element} | ||
> | ||
<BlockquoteIcon element={`${element}_ICON`} decorative={true} color="colorTextIcon" /> | ||
<BlockquoteContext.Provider value={{ url }}> | ||
<Box>{children}</Box> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: is this box wrapping children necessary? |
||
</BlockquoteContext.Provider> | ||
</Box> | ||
); | ||
}, | ||
); | ||
|
||
Blockquote.displayName = "Blockquote"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { Box, type BoxProps, safelySpreadBoxProps } from "@twilio-paste/box"; | ||
import type { HTMLPasteProps } from "@twilio-paste/types"; | ||
import React from "react"; | ||
|
||
import { BlockquoteContext } from "./BlockquoteContext"; | ||
|
||
export interface BlockquoteContentProps extends HTMLPasteProps<"blockquote"> { | ||
children?: React.ReactNode; | ||
/** | ||
* Overrides the default element name to apply unique styles with the Customization Provider | ||
* @default 'BLOCKQUOTE_CONTENT' | ||
* @type {BoxProps['element']} | ||
* @memberof BlockquoteContentProps | ||
*/ | ||
element?: BoxProps["element"]; | ||
} | ||
|
||
export const BlockquoteContent = React.forwardRef<HTMLQuoteElement, BlockquoteContentProps>( | ||
({ children, element = "BLOCKQUOTE_CONTENT", ...props }, ref) => { | ||
const { url } = React.useContext(BlockquoteContext); | ||
|
||
return ( | ||
<Box {...safelySpreadBoxProps(props)} as="blockquote" margin="space0" ref={ref} element={element} cite={url}> | ||
{children} | ||
</Box> | ||
); | ||
}, | ||
); | ||
|
||
BlockquoteContent.displayName = "BlockquoteContent"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import * as React from "react"; | ||
|
||
export interface BlockquoteContextProps { | ||
url?: string; | ||
} | ||
export const BlockquoteContext = React.createContext<BlockquoteContextProps>({} as any); |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,64 @@ | ||||||
import { Anchor } from "@twilio-paste/anchor"; | ||||||
import { Box, type BoxProps } from "@twilio-paste/box"; | ||||||
import { Text } from "@twilio-paste/text"; | ||||||
import type { HTMLPasteProps } from "@twilio-paste/types"; | ||||||
import React from "react"; | ||||||
|
||||||
import { BlockquoteContext } from "./BlockquoteContext"; | ||||||
|
||||||
export interface BlockquoteSourceProps extends HTMLPasteProps<"div"> { | ||||||
/** | ||||||
* Overrides the default element name to apply unique styles with the Customization Provider | ||||||
* @default 'BLOCKQUOTE_SOURCE' | ||||||
* @type {BoxProps['element']} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
right? or am I doing it wrong. |
||||||
* @memberof BlockquoteSourceProps | ||||||
*/ | ||||||
element?: BoxProps["element"]; | ||||||
|
||||||
/** | ||||||
* The author of the quote | ||||||
* @type {string} | ||||||
* @memberof BlockquoteSourceProps | ||||||
*/ | ||||||
author: string; | ||||||
|
||||||
/** | ||||||
* The source of the quote | ||||||
* @type {string} | ||||||
* @memberof BlockquoteSourceProps | ||||||
*/ | ||||||
source?: string; | ||||||
} | ||||||
|
||||||
export const BlockquoteSource = React.forwardRef<HTMLDivElement, BlockquoteSourceProps>( | ||||||
({ element = "BLOCKQUOTE_SOURCE", author, source, ...props }, ref) => { | ||||||
const { url } = React.useContext(BlockquoteContext); | ||||||
|
||||||
return ( | ||||||
<Box marginTop="space30" as="p" element={element} {...props} ref={ref}> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: why the mix of box as p and text as span here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think span is right in this case otherwise they wuld be on separate line. The author and source styling are different |
||||||
—{" "} | ||||||
<Text as="span" fontWeight="fontWeightSemibold" element={`${element}_AUTHOR`}> | ||||||
{author} | ||||||
</Text> | ||||||
{source ? ( | ||||||
<> | ||||||
,{" "} | ||||||
<Box as="cite" fontStyle="normal" element={`${element}_CITE`}> | ||||||
{url ? ( | ||||||
<Anchor href={url} showExternal element={`${element}_ANCHOR`}> | ||||||
{source} | ||||||
</Anchor> | ||||||
) : ( | ||||||
<Text as="span" element={`${element}_TEXT`}> | ||||||
{source} | ||||||
</Text> | ||||||
)} | ||||||
</Box> | ||||||
</> | ||||||
) : null} | ||||||
</Box> | ||||||
); | ||||||
}, | ||||||
); | ||||||
|
||||||
BlockquoteSource.displayName = "BlockquoteSource"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export { Blockquote } from "./Blockquote"; | ||
export type { BlockquoteProps } from "./Blockquote"; | ||
export { BlockquoteContent } from "./BlockquoteContent"; | ||
export type { BlockquoteContentProps } from "./BlockquoteContent"; | ||
export { BlockquoteSource } from "./BlockquoteSource"; | ||
export type { BlockquoteSourceProps } from "./BlockquoteSource"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue(blocking): add core