diff --git a/CHANGELOG.md b/CHANGELOG.md index 5036fe47..b9bdeb86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Newer versions + +See release notes: https://github.com/api-platform/admin/releases + ## 4.0.0 * Compatibility with react-admin v5 diff --git a/api/composer.lock b/api/composer.lock index 8eca2315..036b8d98 100644 --- a/api/composer.lock +++ b/api/composer.lock @@ -2641,16 +2641,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.4.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", "shasum": "" }, "require": { @@ -2658,12 +2658,12 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.4-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" } }, "autoload": { @@ -2688,7 +2688,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" }, "funding": [ { @@ -2704,7 +2704,7 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/doctrine-bridge", @@ -4285,27 +4285,26 @@ }, { "name": "symfony/polyfill-php83", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "86fcae159633351e5fd145d1c47de6c528f8caff" + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/86fcae159633351e5fd145d1c47de6c528f8caff", - "reference": "86fcae159633351e5fd145d1c47de6c528f8caff", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", "shasum": "" }, "require": { - "php": ">=7.1", - "symfony/polyfill-php80": "^1.14" + "php": ">=7.2" }, "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -4342,7 +4341,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" }, "funding": [ { @@ -4358,7 +4357,7 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/property-access", @@ -5366,16 +5365,16 @@ }, { "name": "symfony/translation-contracts", - "version": "v3.4.2", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "43810bdb2ddb5400e5c5e778e27b210a0ca83b6b" + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/43810bdb2ddb5400e5c5e778e27b210a0ca83b6b", - "reference": "43810bdb2ddb5400e5c5e778e27b210a0ca83b6b", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", "shasum": "" }, "require": { @@ -5383,12 +5382,12 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.4-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" } }, "autoload": { @@ -5424,7 +5423,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.4.2" + "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1" }, "funding": [ { @@ -5440,7 +5439,7 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/twig-bridge", @@ -5637,16 +5636,16 @@ }, { "name": "symfony/validator", - "version": "v6.4.4", + "version": "v6.4.11", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "1cf92edc9a94d16275efef949fa6748d11cc8f47" + "reference": "4ff41cf10af1de99ad92895411b55c9f309bc2d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/1cf92edc9a94d16275efef949fa6748d11cc8f47", - "reference": "1cf92edc9a94d16275efef949fa6748d11cc8f47", + "url": "https://api.github.com/repos/symfony/validator/zipball/4ff41cf10af1de99ad92895411b55c9f309bc2d8", + "reference": "4ff41cf10af1de99ad92895411b55c9f309bc2d8", "shasum": "" }, "require": { @@ -5693,7 +5692,8 @@ "Symfony\\Component\\Validator\\": "" }, "exclude-from-classmap": [ - "/Tests/" + "/Tests/", + "/Resources/bin/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -5713,7 +5713,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v6.4.4" + "source": "https://github.com/symfony/validator/tree/v6.4.11" }, "funding": [ { @@ -5729,7 +5729,7 @@ "type": "tidelift" } ], - "time": "2024-02-22T20:27:10+00:00" + "time": "2024-08-30T15:57:55+00:00" }, { "name": "symfony/var-dumper", @@ -6124,30 +6124,38 @@ }, { "name": "twig/twig", - "version": "v3.8.0", + "version": "v3.11.2", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d" + "reference": "5b580ec1882b54c98cbd8c0f8a3ca5d1904db6b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", - "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/5b580ec1882b54c98cbd8c0f8a3ca5d1904db6b1", + "reference": "5b580ec1882b54c98cbd8c0f8a3ca5d1904db6b1", "shasum": "" }, "require": { "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php80": "^1.22" + "symfony/polyfill-php80": "^1.22", + "symfony/polyfill-php81": "^1.29" }, "require-dev": { "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0" + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" }, "type": "library", "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], "psr-4": { "Twig\\": "src/" } @@ -6180,7 +6188,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.8.0" + "source": "https://github.com/twigphp/Twig/tree/v3.11.2" }, "funding": [ { @@ -6192,7 +6200,7 @@ "type": "tidelift" } ], - "time": "2023-11-21T18:54:41+00:00" + "time": "2024-11-06T18:50:16+00:00" }, { "name": "willdurand/negotiation", @@ -6411,7 +6419,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -6419,6 +6427,6 @@ "ext-ctype": "*", "ext-iconv": "*" }, - "platform-dev": {}, - "plugin-api-version": "2.6.0" + "platform-dev": [], + "plugin-api-version": "2.3.0" } diff --git a/api/config/packages/api_platform.yaml b/api/config/packages/api_platform.yaml index d724b344..4dd2e726 100644 --- a/api/config/packages/api_platform.yaml +++ b/api/config/packages/api_platform.yaml @@ -6,6 +6,7 @@ api_platform: include_type: true formats: jsonld: ['application/ld+json'] + json: ["application/json"] docs_formats: jsonld: ['application/ld+json'] jsonopenapi: ['application/vnd.openapi+json'] diff --git a/api/migrations/Version20250306152729.php b/api/migrations/Version20250306152729.php new file mode 100644 index 00000000..fd915fce --- /dev/null +++ b/api/migrations/Version20250306152729.php @@ -0,0 +1,37 @@ +addSql('CREATE TABLE book (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, isbn VARCHAR(255) DEFAULT NULL, title VARCHAR(255) NOT NULL, description TEXT NOT NULL, author VARCHAR(255) NOT NULL, publication_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE review (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, rating SMALLINT NOT NULL, body TEXT NOT NULL, author VARCHAR(255) NOT NULL, publication_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, book_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_794381C616A2B381 ON review (book_id)'); + $this->addSql('ALTER TABLE review ADD CONSTRAINT FK_794381C616A2B381 FOREIGN KEY (book_id) REFERENCES book (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE review DROP CONSTRAINT FK_794381C616A2B381'); + $this->addSql('DROP TABLE book'); + $this->addSql('DROP TABLE review'); + } +} diff --git a/api/src/Entity/Book.php b/api/src/Entity/Book.php new file mode 100644 index 00000000..e51d68a8 --- /dev/null +++ b/api/src/Entity/Book.php @@ -0,0 +1,77 @@ + 'ASC', + 'isbn' => 'ASC', + 'title' => 'ASC', + 'author' => 'ASC', + 'publicationDate' => 'DESC' +])] +#[ApiFilter(SearchFilter::class, properties: [ + 'id' => 'exact', + 'title' => 'ipartial', + 'author' => 'ipartial' +])] +#[ApiFilter(DateFilter::class, properties: ['publicationDate'])] +class Book +{ + /** The ID of this book */ + #[ORM\Id] + #[ORM\Column] + #[ORM\GeneratedValue] + private ?int $id = null; + + /** The ISBN of this book (or null if doesn't have one) */ + #[ORM\Column(nullable: true)] + public ?string $isbn = null; + + /** The title of this book */ + #[ORM\Column] + #[Assert\NotBlank] + #[ApiProperty(iris: ['http://schema.org/name'])] + public string $title = ''; + + /** The description of this book */ + #[ORM\Column(type: 'text')] + #[Assert\NotBlank] + public string $description = ''; + + /** The author of this book */ + #[ORM\Column] + #[Assert\NotBlank] + public string $author = ''; + + /** The publication date of this book */ + #[ORM\Column] + #[Assert\NotNull] + public ?\DateTimeImmutable $publicationDate = null; + + /** @var Review[] Available reviews for this book */ + #[ORM\OneToMany(mappedBy: 'book', targetEntity: Review::class, cascade: ['persist', 'remove'])] + public iterable $reviews; + + public function __construct() + { + $this->reviews = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } +} diff --git a/api/src/Entity/Review.php b/api/src/Entity/Review.php new file mode 100644 index 00000000..dc7de3f2 --- /dev/null +++ b/api/src/Entity/Review.php @@ -0,0 +1,69 @@ + 'ASC', + 'rating' => 'ASC', + 'author' => 'ASC', + 'publicationDate' => 'DESC' +])] +#[ApiFilter(SearchFilter::class, properties: [ + 'id' => 'exact', + 'body' => 'ipartial', + 'author' => 'ipartial' +])] +#[ApiFilter(NumericFilter::class, properties: ['rating'])] +#[ApiFilter(DateFilter::class, properties: ['publicationDate'])] +class Review +{ + /** The ID of this review */ + #[ORM\Id] + #[ORM\Column] + #[ORM\GeneratedValue] + private ?int $id = null; + + /** The rating of this review (between 0 and 5) */ + #[ORM\Column(type: 'smallint')] + #[Assert\Range(min: 0, max: 5)] + public int $rating = 0; + + /** The body of this review */ + #[ORM\Column(type: 'text')] + #[Assert\NotBlank] + public string $body = ''; + + /** The author of this review */ + #[ORM\Column] + #[Assert\NotBlank] + public string $author = ''; + + /** The publication date of this review */ + #[ORM\Column] + #[Assert\NotNull] + #[ApiProperty(iris: ['http://schema.org/name'])] + public ?\DateTimeImmutable $publicationDate = null; + + /** The book this review is about */ + #[ORM\ManyToOne(inversedBy: 'reviews')] + #[Assert\NotNull] + public ?Book $book = null; + + public function getId(): ?int + { + return $this->id; + } +} diff --git a/package.json b/package.json index da6a3cc2..f7136f8e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@api-platform/admin", - "version": "4.0.6", + "version": "4.0.7", "description": "Automatic administration interface for Hydra-enabled APIs.", "files": [ "*.md", diff --git a/src/core/ResourceGuesser.test.tsx b/src/core/ResourceGuesser.test.tsx index 8fac3b7c..66f7766d 100644 --- a/src/core/ResourceGuesser.test.tsx +++ b/src/core/ResourceGuesser.test.tsx @@ -1,6 +1,65 @@ import React from 'react'; +import { + AdminContext, + useGetOne, + useGetRecordRepresentation, +} from 'react-admin'; import ReactTestRenderer from 'react-test-renderer/shallow'; +import { Resource } from '@api-platform/api-doc-parser'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; import ResourceGuesser from './ResourceGuesser.js'; +import SchemaAnalyzerContext from '../introspection/SchemaAnalyzerContext.js'; +import schemaAnalyzer from '../hydra/schemaAnalyzer.js'; +import type { + ApiPlatformAdminDataProvider, + ApiPlatformAdminRecord, +} from '../types.js'; +import { API_FIELDS_DATA } from '../__fixtures__/parsedData.js'; + +const hydraSchemaAnalyzer = schemaAnalyzer(); +const dataProvider: ApiPlatformAdminDataProvider = { + getList: () => Promise.resolve({ data: [], total: 0 }), + getMany: () => Promise.resolve({ data: [] }), + getManyReference: () => Promise.resolve({ data: [], total: 0 }), + update: () => + Promise.resolve({ data: { id: 'id' } } as { data: RecordType }), + updateMany: () => Promise.resolve({ data: [] }), + create: () => + Promise.resolve({ data: { id: 'id' } } as { data: RecordType }), + delete: () => + Promise.resolve({ data: { id: 'id' } } as { data: RecordType }), + deleteMany: () => Promise.resolve({ data: [] }), + getOne: () => + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + Promise.resolve({ + data: { + id: '/users/123', + fieldA: 'fieldA value', + fieldB: 'fieldB value', + deprecatedField: 'deprecatedField value', + title: 'Title', + body: 'Body', + }, + }), + introspect: () => + Promise.resolve({ + data: { + entrypoint: 'entrypoint', + resources: [ + new Resource('users', '/users', { + fields: API_FIELDS_DATA, + readableFields: API_FIELDS_DATA, + writableFields: API_FIELDS_DATA, + parameters: [], + }), + ], + }, + }), + subscribe: () => Promise.resolve({ data: null }), + unsubscribe: () => Promise.resolve({ data: null }), +}; describe('', () => { const renderer = ReactTestRenderer.createRenderer(); @@ -60,4 +119,28 @@ describe('', () => { expect(renderer.getRenderOutput()).toMatchSnapshot(); }); + + test('supports recordRepresentation', async () => { + const TestComponent = () => { + const { data: user } = useGetOne('users', { id: '/users/123' }); + const getRecordRepresentation = useGetRecordRepresentation('users'); + if (!user) { + return 'loading'; + } + return getRecordRepresentation(user); + }; + render( + + + } + recordRepresentation="fieldA" + /> + + , + ); + + await screen.findByText('fieldA value'); + }); }); diff --git a/src/core/ResourceGuesser.tsx b/src/core/ResourceGuesser.tsx index 6f74ab4d..f47c8151 100644 --- a/src/core/ResourceGuesser.tsx +++ b/src/core/ResourceGuesser.tsx @@ -58,6 +58,7 @@ export const IntrospectedResourceGuesser = ({ name: resource, icon: props.icon, options: props.options, + recordRepresentation: props.recordRepresentation, hasList, hasEdit, hasCreate, @@ -69,6 +70,7 @@ export const IntrospectedResourceGuesser = ({ resource, props.icon, props.options, + props.recordRepresentation, hasList, hasEdit, hasCreate, @@ -104,6 +106,7 @@ ResourceGuesser.registerResource = ( name: props.name, icon: props.icon, options: props.options, + recordRepresentation: props.recordRepresentation, hasList: true, hasEdit: true, hasCreate: true, diff --git a/src/hydra/fetchHydra.test.ts b/src/hydra/fetchHydra.test.ts index a3c2dfd0..d1f9e033 100644 --- a/src/hydra/fetchHydra.test.ts +++ b/src/hydra/fetchHydra.test.ts @@ -55,6 +55,32 @@ test.each([ 'At least one product must be selected if policy is restricted.', }, ], + [ + 'problem+json', + { + '@context': '/contexts/ConstraintViolation', + '@id': '/validation_errors/2881c032-660f-46b6-8153-d352d9706640', + '@type': 'ConstraintViolation', + status: 422, + violations: [ + { + propertyPath: 'isbn', + 'ConstraintViolation/message': + 'This value is neither a valid ISBN-10 nor a valid ISBN-13.', + 'ConstraintViolation/code': '2881c032-660f-46b6-8153-d352d9706640', + }, + ], + detail: + 'isbn: This value is neither a valid ISBN-10 nor a valid ISBN-13.', + description: + 'isbn: This value is neither a valid ISBN-10 nor a valid ISBN-13.', + type: '/validation_errors/2881c032-660f-46b6-8153-d352d9706640', + title: 'An error occurred', + }, + { + isbn: 'This value is neither a valid ISBN-10 nor a valid ISBN-13.', + }, + ], ])( '%s violation list expanding', async (format: string, resBody: object, expected: object) => { diff --git a/src/hydra/schemaAnalyzer.ts b/src/hydra/schemaAnalyzer.ts index 36b8da86..082cb3e3 100644 --- a/src/hydra/schemaAnalyzer.ts +++ b/src/hydra/schemaAnalyzer.ts @@ -68,6 +68,10 @@ const getFieldType = (field: Field) => { } }; +const getViolationMessage = (violation: JsonLdObj, base: string) => + violation[`${base}#message`] ?? + violation[`${base}#ConstraintViolation/message`]; + const getSubmissionErrors = (error: HttpError) => { if (!error.body?.[0]) { return null; @@ -84,15 +88,16 @@ const getSubmissionErrors = (error: HttpError) => { const violations: SubmissionErrors = content[violationKey].reduce( (previousViolations: SubmissionErrors, violation: JsonLdObj) => - !violation[`${base}#propertyPath`] || !violation[`${base}#message`] + !violation[`${base}#propertyPath`] || + !getViolationMessage(violation, base) ? previousViolations : { ...previousViolations, [(violation[`${base}#propertyPath`] as JsonLdObj[])[0]?.[ '@value' - ] as string]: (violation[`${base}#message`] as JsonLdObj[])[0]?.[ - '@value' - ], + ] as string]: ( + getViolationMessage(violation, base) as JsonLdObj[] + )[0]?.['@value'], }, {}, ); diff --git a/src/stories/Basic.stories.ts b/src/stories/Basic.stories.ts deleted file mode 100644 index 8fe6c89c..00000000 --- a/src/stories/Basic.stories.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; -import { within } from '@storybook/test'; -import Basic from './Basic'; - -const meta = { - title: 'Admin/Basic', - component: Basic, - tags: ['autodocs'], - parameters: { - layout: 'fullscreen', - }, -} satisfies Meta; - -export default meta; - -type Story = StoryObj; - -export const Admin: Story = { - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - await canvas.findByText('Greetings'); - }, - args: { - entrypoint: process.env.ENTRYPOINT, - }, -}; diff --git a/src/stories/Basic.stories.tsx b/src/stories/Basic.stories.tsx new file mode 100644 index 00000000..a086bd37 --- /dev/null +++ b/src/stories/Basic.stories.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { within } from '@storybook/test'; +import { HydraAdmin, type HydraAdminProps } from '../hydra'; +import { OpenApiAdmin } from '../openapi'; + +/** + * # Basic `` component + * The `` component without any parameter. + */ +const Basic = ({ entrypoint }: BasicProps) => ( + +); + +interface BasicProps extends Pick {} + +const meta = { + title: 'Admin/Basic', + component: Basic, + parameters: { + layout: 'fullscreen', + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Hydra: Story = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await canvas.findByText('Greetings'); + }, + args: { + entrypoint: process.env.ENTRYPOINT, + }, +}; + +export const OpenApi = () => ( + +); diff --git a/src/stories/Configure.mdx b/src/stories/Configure.mdx deleted file mode 100644 index cd58d037..00000000 --- a/src/stories/Configure.mdx +++ /dev/null @@ -1,3 +0,0 @@ -import { Meta } from "@storybook/blocks"; - - \ No newline at end of file diff --git a/src/stories/Custom.stories.tsx b/src/stories/Custom.stories.tsx deleted file mode 100644 index a68b6281..00000000 --- a/src/stories/Custom.stories.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import { HydraAdmin } from '../hydra'; -import ResourceGuesser from '../core/ResourceGuesser'; -import ListGuesser from '../list/ListGuesser'; -import ShowGuesser from '../show/ShowGuesser'; -import FieldGuesser from '../field/FieldGuesser'; -import EditGuesser from '../edit/EditGuesser'; -import InputGuesser from '../input/InputGuesser'; -import CreateGuesser from '../create/CreateGuesser'; - -export default { - title: 'Admin/Custom', - parameters: { - layout: 'fullscreen', - }, -}; - -const GreetingList = () => ( - - - -); - -const GreetingShow = () => ( - - - -); - -const GreetingEdit = () => ( - - - -); - -const GreetingCreate = () => ( - - - -); - -export const Custom = () => ( - - - -); diff --git a/src/stories/custom/AdvancedCustomization.stories.tsx b/src/stories/custom/AdvancedCustomization.stories.tsx new file mode 100644 index 00000000..7955b16d --- /dev/null +++ b/src/stories/custom/AdvancedCustomization.stories.tsx @@ -0,0 +1,247 @@ +import AutoStoriesIcon from '@mui/icons-material/AutoStories'; +import ReviewsIcon from '@mui/icons-material/Reviews'; +import { Rating, Stack } from '@mui/material'; +import React from 'react'; +import type { InputProps } from 'react-admin'; +import { + AutocompleteInput, + Create, + Datagrid, + DateField, + Edit, + Labeled, + Layout, + List, + NumberField, + ReferenceArrayField, + ReferenceField, + ReferenceInput, + Show, + SimpleForm, + SimpleList, + SimpleShowLayout, + TabbedShowLayout, + TextField, + TextInput, + WithRecord, + WrapperField, + defaultDarkTheme, + defaultLightTheme, + required, + useInput, +} from 'react-admin'; +import ResourceGuesser from '../../core/ResourceGuesser'; +import FieldGuesser from '../../field/FieldGuesser'; +import { HydraAdmin } from '../../hydra'; +import InputGuesser from '../../input/InputGuesser'; + +export default { + title: 'Admin/Custom/AdvancedCustomization', + parameters: { + layout: 'fullscreen', + }, +}; + +const BookCreate = () => ( + + + + + + + + + + + +); + +const BookEdit = () => ( + + + + + + + + + + + +); + +const BookShow = () => ( + + + + + + + + + + + + + + + review.author + .split(' ') + .map((name: string) => name[0]) + .join('') + } + // eslint-disable-next-line react/no-unstable-nested-components + tertiaryText={(review) => ( + + )} + /> + + + + +); + +const BookList = () => ( + + + + + + + + + +); + +const RatingInput = (props: InputProps) => { + const { field } = useInput(props); + return ( + { + field.onChange(value); + }} + /> + ); +}; + +const filterToBookQuery = (searchText: string) => ({ + title: `%${searchText}%`, +}); + +const ReviewCreate = () => ( + + + + + + + + + + + + +); + +const ReviewEdit = () => ( + + + + + + + + + + + + + + + +); + +const ReviewShow = () => ( + + + + + + + ( + + )} + /> + + + + +); + +const ReviewList = () => ( + + + + + + ( + + )} + /> + + + + +); + +export const AdvancedCustomization = () => ( + + + + +); diff --git a/src/stories/custom/UsingGuessers.stories.tsx b/src/stories/custom/UsingGuessers.stories.tsx new file mode 100644 index 00000000..79a59bee --- /dev/null +++ b/src/stories/custom/UsingGuessers.stories.tsx @@ -0,0 +1,119 @@ +import AutoStoriesIcon from '@mui/icons-material/AutoStories'; +import ReviewsIcon from '@mui/icons-material/Reviews'; +import React from 'react'; +import { HydraAdmin } from '../../hydra'; +import ResourceGuesser from '../../core/ResourceGuesser'; +import ListGuesser from '../../list/ListGuesser'; +import ShowGuesser from '../../show/ShowGuesser'; +import FieldGuesser from '../../field/FieldGuesser'; +import EditGuesser from '../../edit/EditGuesser'; +import InputGuesser from '../../input/InputGuesser'; +import CreateGuesser from '../../create/CreateGuesser'; + +export default { + title: 'Admin/Custom/UsingGuessers', + parameters: { + layout: 'fullscreen', + }, +}; + +const BookCreate = () => ( + + + + + + + +); + +const BookEdit = () => ( + + + + + + + +); + +const BookShow = () => ( + + + + + + + + +); + +const BookList = () => ( + + + + + + + +); + +const ReviewCreate = () => ( + + + + + + + +); + +const ReviewEdit = () => ( + + + + + + + +); + +const ReviewShow = () => ( + + + + + + + +); + +const ReviewList = () => ( + + + + + + +); + +export const UsingGuessers = () => ( + + + + +); diff --git a/yarn.lock b/yarn.lock index bca53bc7..aef3fb37 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3433,9 +3433,9 @@ axe-core@^4.10.0: integrity sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w== axios@^1.6.1: - version "1.7.7" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" - integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== + version "1.8.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.8.2.tgz#fabe06e241dfe83071d4edfbcaa7b1c3a40f7979" + integrity sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg== dependencies: follow-redirects "^1.15.6" form-data "^4.0.0" @@ -9882,9 +9882,9 @@ undici-types@~6.19.2, undici-types@~6.19.8: integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== undici@^5.21.2: - version "5.28.4" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068" - integrity sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g== + version "5.28.5" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.5.tgz#b2b94b6bf8f1d919bc5a6f31f2c01deb02e54d4b" + integrity sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA== dependencies: "@fastify/busboy" "^2.0.0" pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy